├── .gitignore ├── replace └── replace.go ├── debug_helpers.go ├── rpc ├── languageserver │ ├── LanguageServerState.proto │ ├── LanguageServerState_grpc.pb.go │ └── LanguageServerState.pb.go ├── client │ └── client.go └── server │ └── server.go ├── read_write_closer.go ├── ctx_cmd.go ├── Makefile ├── completion_cmd.go ├── lsp └── lsp_helpers.go ├── README.md ├── docstate └── docstate.go ├── llm.go ├── language_server_test.go ├── liveconf └── liveconf.go ├── cmd └── test-ls-files │ └── main.go ├── main.go ├── dev_cmds.go ├── llm_context_test.go ├── go.mod ├── llm_context.go ├── config.go ├── locality └── locality.go ├── index_cmd.go ├── db.go ├── code_actions.go ├── language_server_cmd.go └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | third_party 3 | -------------------------------------------------------------------------------- /replace/replace.go: -------------------------------------------------------------------------------- 1 | package replace 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | "syscall" 7 | ) 8 | 9 | func Exec(binary string, args ...string) { 10 | var err error 11 | binary, err = exec.LookPath(binary) 12 | if err != nil { 13 | panic(err) 14 | } 15 | 16 | // get the system's environment variables 17 | environment := os.Environ() 18 | 19 | // get a slice of the pieces of the command 20 | command := append([]string{binary}, args...) 21 | 22 | err = syscall.Exec(binary, command, environment) 23 | if err != nil { 24 | panic(err) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /debug_helpers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | ) 8 | 9 | type ReadLogger struct { 10 | io.ReadCloser 11 | } 12 | 13 | func (rl *ReadLogger) Read(p []byte) (n int, err error) { 14 | n, err = rl.ReadCloser.Read(p) 15 | fmt.Fprintln(os.Stderr, "READ", string(p)) 16 | return n, err 17 | } 18 | 19 | func (rl *ReadLogger) Close() error { 20 | return rl.Close() 21 | } 22 | 23 | type WriteLogger struct { 24 | io.Writer 25 | } 26 | 27 | func (wl *WriteLogger) Write(p []byte) (n int, err error) { 28 | fmt.Fprintln(os.Stderr, "WRITING", string(p)) 29 | return wl.Writer.Write(p) 30 | } 31 | -------------------------------------------------------------------------------- /rpc/languageserver/LanguageServerState.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package languageserver; 4 | option go_package = "github.com/everestmz/sage/rpc/languageserver"; 5 | 6 | import "google/protobuf/timestamp.proto"; 7 | 8 | message TextDocument { 9 | string uri = 1; 10 | string language_id = 2; 11 | int32 version = 3; 12 | string text = 4; 13 | google.protobuf.Timestamp last_edit = 5; 14 | int32 last_edited_line = 6; 15 | } 16 | 17 | message GetOpenDocumentsRequest {} 18 | 19 | message GetOpenDocumentsResponse { 20 | map documents = 1; 21 | } 22 | 23 | service LanguageServerState { 24 | rpc GetOpenDocuments(GetOpenDocumentsRequest) returns (GetOpenDocumentsResponse) {} 25 | } 26 | -------------------------------------------------------------------------------- /read_write_closer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "os" 6 | ) 7 | 8 | type CombinedReadWriteCloser struct { 9 | io.Reader 10 | io.Writer 11 | Closer func() error 12 | 13 | readFile *os.File 14 | writeFile *os.File 15 | } 16 | 17 | func (c *CombinedReadWriteCloser) Read(p []byte) (int, error) { 18 | read, err := c.Reader.Read(p) 19 | 20 | if c.readFile != nil { 21 | c.readFile.Write(p) 22 | } 23 | 24 | return read, err 25 | } 26 | 27 | func (c *CombinedReadWriteCloser) Write(p []byte) (int, error) { 28 | if c.writeFile != nil { 29 | c.writeFile.Write(p) 30 | } 31 | return c.Writer.Write(p) 32 | } 33 | 34 | func (c *CombinedReadWriteCloser) Close() error { 35 | if c.readFile != nil { 36 | c.readFile.Close() 37 | } 38 | 39 | if c.writeFile != nil { 40 | c.writeFile.Close() 41 | } 42 | 43 | return c.Closer() 44 | } 45 | -------------------------------------------------------------------------------- /ctx_cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/everestmz/sage/replace" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func init() { 12 | flags := CtxCmd.PersistentFlags() 13 | flags.BoolP("print", "p", false, "Always just print the filepath even if an editor is present") 14 | } 15 | 16 | var CtxCmd = &cobra.Command{ 17 | Use: "ctx", 18 | Short: "Opens the context file for the current directory in $EDITOR if it exists, otherwise prints its filepath", 19 | RunE: func(cmd *cobra.Command, args []string) error { 20 | flags := cmd.Flags() 21 | 22 | editor := os.Getenv("EDITOR") 23 | 24 | path := getWorkspaceContextPath() 25 | 26 | print, err := flags.GetBool("print") 27 | if err != nil { 28 | return err 29 | } 30 | 31 | if editor == "" || print { 32 | fmt.Println(path) 33 | } 34 | 35 | replace.Exec(editor, path) 36 | return nil 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: bin/sage 2 | 3 | # Set install location 4 | PREFIX ?= /usr/local 5 | INSTALL_PATH = $(PREFIX)/bin 6 | 7 | bin/sage: 8 | go build -o bin/sage . 9 | 10 | generate: 11 | protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative rpc/languageserver/LanguageServerState.proto 12 | 13 | install: bin/sage 14 | @echo "Installing to $(INSTALL_PATH)/sage..." 15 | @mkdir -p $(INSTALL_PATH) 16 | @install -m 755 bin/sage $(INSTALL_PATH)/sage 17 | 18 | # TODO: fix this up, we're not using vss right now so should be able to get rid of 19 | third_party: 20 | mkdir -p third_party/lib 21 | curl -Lo ./third_party/lib/sqlite_vss0.dylib https://github.com/asg017/sqlite-vss/releases/download/v0.1.2/sqlite-vss-v0.1.2-deno-darwin-aarch64.vss0.dylib 22 | curl -Lo ./third_party/lib/sqlite_vector0.dylib https://github.com/asg017/sqlite-vss/releases/download/v0.1.2/sqlite-vss-v0.1.2-deno-darwin-aarch64.vector0.dylib 23 | 24 | clean: 25 | rm -rf bin third_party 26 | -------------------------------------------------------------------------------- /rpc/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "google.golang.org/grpc" 6 | "google.golang.org/grpc/credentials/insecure" 7 | pb "github.com/everestmz/sage/rpc/languageserver" 8 | ) 9 | 10 | // Client represents a client to access the language server state 11 | type Client struct { 12 | conn *grpc.ClientConn 13 | client pb.LanguageServerStateClient 14 | } 15 | 16 | // NewClient creates a new client connected to the language server state 17 | func NewClient(socketPath string) (*Client, error) { 18 | conn, err := grpc.Dial( 19 | "unix://"+socketPath, 20 | grpc.WithTransportCredentials(insecure.NewCredentials()), 21 | ) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | return &Client{ 27 | conn: conn, 28 | client: pb.NewLanguageServerStateClient(conn), 29 | }, nil 30 | } 31 | 32 | // GetOpenDocuments retrieves all currently open documents from the language server 33 | func (c *Client) GetOpenDocuments(ctx context.Context) (map[string]*pb.TextDocument, error) { 34 | response, err := c.client.GetOpenDocuments(ctx, &pb.GetOpenDocumentsRequest{}) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return response.Documents, nil 39 | } 40 | 41 | // Close closes the client connection 42 | func (c *Client) Close() error { 43 | return c.conn.Close() 44 | } 45 | -------------------------------------------------------------------------------- /completion_cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | "time" 10 | 11 | "github.com/everestmz/sage/rpc/client" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var CompletionCmd = &cobra.Command{ 16 | Use: "complete", 17 | Short: "Run a question within the context of the working directory", 18 | Aliases: []string{"c", "ask"}, 19 | RunE: func(cmd *cobra.Command, args []string) error { 20 | var stdinLines []string 21 | if len(args) > 0 { 22 | stdinLines = args 23 | } else { 24 | input, err := io.ReadAll(os.Stdin) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | stdinLines = strings.Split(string(input), "\n") 30 | } 31 | 32 | fmt.Println("Your input was:") 33 | for _, line := range stdinLines { 34 | time.Sleep(time.Second) 35 | fmt.Println(line) 36 | } 37 | 38 | wd, err := os.Getwd() 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | socketPath := getWorkspaceSocketPath(wd) 44 | 45 | lspClient, err := client.NewClient(socketPath) 46 | if err != nil { 47 | return err 48 | } 49 | openDocs, err := lspClient.GetOpenDocuments(context.TODO()) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | fmt.Println("open docs:") 55 | for _, f := range openDocs { 56 | fmt.Println(f.LastEdit.AsTime(), f.Uri) 57 | } 58 | 59 | return nil 60 | }, 61 | } 62 | -------------------------------------------------------------------------------- /lsp/lsp_helpers.go: -------------------------------------------------------------------------------- 1 | package lsp 2 | 3 | import ( 4 | "strings" 5 | 6 | "go.lsp.dev/protocol" 7 | ) 8 | 9 | func TreeSymKindToLspKind(kind string) protocol.SymbolKind { 10 | switch kind { 11 | case "class": 12 | return protocol.SymbolKindClass 13 | case "function": 14 | return protocol.SymbolKindFunction 15 | case "method": 16 | return protocol.SymbolKindMethod 17 | case "type": 18 | // NOTE: this is what typescript-language-server calls a typedef. /shrug 19 | return protocol.SymbolKindVariable 20 | case "module": 21 | return protocol.SymbolKindModule 22 | case "interface": 23 | return protocol.SymbolKindInterface 24 | case "enum": 25 | return protocol.SymbolKindEnum 26 | case "macro": 27 | // XXX: this is definitely wrong 28 | fallthrough 29 | default: 30 | return protocol.SymbolKindVariable 31 | } 32 | } 33 | 34 | func GetRangeFromFile(text string, locationRange protocol.Range) string { 35 | lines := strings.Split(text, "\n") 36 | 37 | var snippetLines []string 38 | 39 | start := locationRange.Start 40 | end := locationRange.End 41 | 42 | snippetLines = append(snippetLines, lines[start.Line][start.Character:]) 43 | 44 | for i := start.Line + 1; i < end.Line; i++ { 45 | snippetLines = append(snippetLines, lines[i]) 46 | } 47 | 48 | snippetLines = append(snippetLines, lines[end.Line][:end.Character]) 49 | 50 | return strings.Join(snippetLines, "\n") 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sage 2 | 3 | Integrate LLMS into any editor that supports LSP. 4 | 5 | [Read more about how & why I built sage.](https://ev.dev/posts/introducing-sage) 6 | 7 | Sage started off as a project to add features like LLM support to [Helix](https://github.com/helix-editor/helix), and enable features like workspace symbol search in large codebases I was working with that crippled language servers like `pyright`. 8 | 9 | It's since grown into an AI multitool for any editor that supports language servers. 10 | 11 | ## Features 12 | 13 | ### Smart context generation 14 | 15 | Dynamically includes the most relevant symbols in the context window, using a combination of [tree-sitter](https://tree-sitter.github.io/tree-sitter/) and LSP clients. No need to manually `@mention` functions or types to include them in context! 16 | 17 | ### LLM completions 18 | 19 | Uses [ollama](https://github.com/ollama/ollama) to power on-device code completion and LLM integrations. 20 | 21 | ![code action-based LLM completions](https://everestmz.github.io/assets/images/sage-demo.gif) 22 | 23 | ### Cursor support 24 | 25 | Supports using LLMs hosted on Cursor's cloud (via [cursor-rpc](https://github.com/everestmz/cursor-rpc). 26 | 27 | ### Instant workspace symbol search 28 | 29 | Sage can index a repository (using [llmcat](https://github.com/everestmz/llmcat)) to power global symbol search for language servers that don't support it (like `pylsp`), or for repos that are too large. 30 | 31 | It can index a repository with >10M symbols (~20M LOC) in a few minutes, and searches through even gigabytes of symbols in milliseconds: 32 | 33 | [![asciicast](https://asciinema.org/a/fhkTWEdRr7sqDgS5ZZtl5UUcQ.svg)](https://asciinema.org/a/fhkTWEdRr7sqDgS5ZZtl5UUcQ) 34 | -------------------------------------------------------------------------------- /rpc/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net" 7 | "os" 8 | 9 | "github.com/everestmz/sage/docstate" 10 | pb "github.com/everestmz/sage/rpc/languageserver" 11 | "google.golang.org/grpc" 12 | "google.golang.org/protobuf/types/known/timestamppb" 13 | ) 14 | 15 | type StateServer struct { 16 | pb.UnimplementedLanguageServerStateServer 17 | ds *docstate.DocumentState 18 | } 19 | 20 | func NewStateServer(ds *docstate.DocumentState) *StateServer { 21 | return &StateServer{ds: ds} 22 | } 23 | 24 | func (s *StateServer) GetOpenDocuments(ctx context.Context, req *pb.GetOpenDocumentsRequest) (*pb.GetOpenDocumentsResponse, error) { 25 | response := &pb.GetOpenDocumentsResponse{ 26 | Documents: make(map[string]*pb.TextDocument), 27 | } 28 | 29 | for uri, doc := range s.ds.OpenDocuments() { 30 | response.Documents[string(uri)] = &pb.TextDocument{ 31 | Uri: string(uri), 32 | LanguageId: string(doc.LanguageID), 33 | Version: int32(doc.Version), 34 | Text: doc.Text, 35 | LastEdit: timestamppb.New(doc.LastEdit), 36 | LastEditedLine: int32(doc.LastEditedLine), 37 | } 38 | } 39 | 40 | return response, nil 41 | } 42 | 43 | // StartStateServer starts the gRPC server on a Unix domain socket 44 | func StartStateServer(ds *docstate.DocumentState, socketPath string) error { 45 | // Clean up existing socket if it exists 46 | if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) { 47 | return err 48 | } 49 | 50 | listener, err := net.Listen("unix", socketPath) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | server := grpc.NewServer() 56 | pb.RegisterLanguageServerStateServer(server, NewStateServer(ds)) 57 | 58 | go func() { 59 | if err := server.Serve(listener); err != nil { 60 | log.Printf("Failed to serve: %v", err) 61 | } 62 | }() 63 | 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /docstate/docstate.go: -------------------------------------------------------------------------------- 1 | package docstate 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "go.lsp.dev/protocol" 8 | "go.lsp.dev/uri" 9 | ) 10 | 11 | type OpenDocument struct { 12 | protocol.TextDocumentItem 13 | 14 | LastEdit time.Time 15 | LastEditedLine int 16 | } 17 | 18 | func NewDocumentState() *DocumentState { 19 | return &DocumentState{ 20 | openDocuments: map[uri.URI]*OpenDocument{}, 21 | docLock: sync.Mutex{}, 22 | } 23 | } 24 | 25 | type DocumentState struct { 26 | openDocuments map[uri.URI]*OpenDocument 27 | docLock sync.Mutex 28 | } 29 | 30 | func (ds *DocumentState) OpenDocuments() map[uri.URI]OpenDocument { 31 | ds.docLock.Lock() 32 | defer ds.docLock.Unlock() 33 | 34 | docs := map[uri.URI]OpenDocument{} 35 | for uri, doc := range ds.openDocuments { 36 | docs[uri] = *doc 37 | } 38 | 39 | return docs 40 | } 41 | 42 | func (ds *DocumentState) GetOpenDocument(uri uri.URI) (OpenDocument, bool) { 43 | ds.docLock.Lock() 44 | defer ds.docLock.Unlock() 45 | 46 | doc, ok := ds.openDocuments[uri] 47 | if ok { 48 | return *doc, true 49 | } 50 | 51 | return OpenDocument{}, false 52 | } 53 | 54 | func (ds *DocumentState) OpenDocument(doc *protocol.TextDocumentItem) { 55 | ds.docLock.Lock() 56 | defer ds.docLock.Unlock() 57 | 58 | ds.openDocuments[doc.URI] = &OpenDocument{ 59 | TextDocumentItem: *doc, 60 | 61 | LastEdit: time.Now(), 62 | LastEditedLine: 0, 63 | } 64 | } 65 | 66 | func (ds *DocumentState) CloseDocument(uri uri.URI) { 67 | ds.docLock.Lock() 68 | defer ds.docLock.Unlock() 69 | 70 | delete(ds.openDocuments, uri) 71 | } 72 | 73 | func (ds *DocumentState) EditDocument(uri uri.URI, editFunc func(doc *protocol.TextDocumentItem) error) error { 74 | ds.docLock.Lock() 75 | defer ds.docLock.Unlock() 76 | 77 | openDoc := ds.openDocuments[uri] 78 | protocolDoc := openDoc.TextDocumentItem 79 | err := editFunc(&protocolDoc) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | openDoc.TextDocumentItem = protocolDoc 85 | 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /llm.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "path/filepath" 6 | 7 | ollama "github.com/ollama/ollama/api" 8 | "github.com/rs/zerolog" 9 | "gopkg.in/natefinch/lumberjack.v2" 10 | ) 11 | 12 | var llmLogger = zerolog.New(&lumberjack.Logger{ 13 | Filename: filepath.Join(getConfigDir(), "llm.log"), 14 | MaxSize: 50, 15 | MaxBackups: 10, 16 | }).With().Timestamp().Logger() 17 | 18 | type LLMClient struct { 19 | ol *ollama.Client 20 | } 21 | 22 | func NewLLMClient() (*LLMClient, error) { 23 | ol, err := ollama.ClientFromEnvironment() 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | return &LLMClient{ 29 | ol: ol, 30 | }, nil 31 | } 32 | 33 | type CompletionResponse struct { 34 | Text string 35 | Done bool 36 | } 37 | 38 | type GenerateResponseFunc = func(CompletionResponse) error 39 | 40 | func (lc *LLMClient) StreamCompletion(ctx context.Context, model, text string, handler GenerateResponseFunc) error { 41 | stream := true 42 | 43 | output := "" 44 | defer func(resp *string) { 45 | llmLogger.Info(). 46 | Str("model", model). 47 | Str("prompt", text). 48 | Str("response", *resp). 49 | Msg("Finished streaming completion") 50 | }(&output) 51 | 52 | return lc.ol.Generate(ctx, &ollama.GenerateRequest{ 53 | Model: model, 54 | Prompt: text, 55 | Stream: &stream, 56 | }, func(gr ollama.GenerateResponse) error { 57 | output += gr.Response 58 | return handler(CompletionResponse{ 59 | Text: gr.Response, 60 | Done: gr.Done, 61 | }) 62 | }) 63 | } 64 | 65 | func (lc *LLMClient) GenerateCompletion(ctx context.Context, model, text string) (string, error) { 66 | stream := false 67 | 68 | var completion string 69 | 70 | err := lc.ol.Generate(ctx, &ollama.GenerateRequest{ 71 | Model: model, 72 | Prompt: text, 73 | Stream: &stream, 74 | }, func(gr ollama.GenerateResponse) error { 75 | completion = gr.Response 76 | return nil 77 | }) 78 | 79 | llmLogger.Info(). 80 | Str("model", model). 81 | Str("prompt", text). 82 | Str("response", completion). 83 | Msg("Finished completion") 84 | 85 | return completion, err 86 | } 87 | 88 | func (lc *LLMClient) GetEmbedding(ctx context.Context, model, text string) ([]float64, error) { 89 | resp, err := lc.ol.Embeddings(ctx, &ollama.EmbeddingRequest{ 90 | Model: model, 91 | Prompt: text, 92 | }) 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | return resp.Embedding, nil 98 | } 99 | -------------------------------------------------------------------------------- /language_server_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "go.lsp.dev/protocol" 7 | ) 8 | 9 | func TestApplyChangesToDocument(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | initialDoc string 13 | changes []protocol.TextDocumentContentChangeEvent 14 | expectedResult string 15 | expectError bool 16 | }{ 17 | { 18 | name: "Single line addition", 19 | initialDoc: "Hello\nWorld\n", 20 | changes: []protocol.TextDocumentContentChangeEvent{ 21 | { 22 | Range: protocol.Range{ 23 | Start: protocol.Position{Line: 0, Character: 5}, 24 | End: protocol.Position{Line: 0, Character: 5}, 25 | }, 26 | Text: " there", 27 | }, 28 | }, 29 | expectedResult: "Hello there\nWorld\n", 30 | expectError: false, 31 | }, 32 | { 33 | name: "New line addition", 34 | initialDoc: "Hello\nWorld\n", 35 | changes: []protocol.TextDocumentContentChangeEvent{ 36 | { 37 | Range: protocol.Range{ 38 | Start: protocol.Position{Line: 1, Character: 0}, 39 | End: protocol.Position{Line: 1, Character: 0}, 40 | }, 41 | Text: "\n", 42 | }, 43 | }, 44 | expectedResult: "Hello\n\nWorld\n", 45 | expectError: false, 46 | }, 47 | { 48 | name: "Multi-line change", 49 | initialDoc: "Line 1\nLine 2\nLine 3\n", 50 | changes: []protocol.TextDocumentContentChangeEvent{ 51 | { 52 | Range: protocol.Range{ 53 | Start: protocol.Position{Line: 0, Character: 6}, 54 | End: protocol.Position{Line: 2, Character: 0}, 55 | }, 56 | Text: " modified\nNew", 57 | }, 58 | }, 59 | expectedResult: "Line 1 modified\nNewLine 3\n", 60 | expectError: false, 61 | }, 62 | { 63 | name: "Out of bounds change", 64 | initialDoc: "Single line", 65 | changes: []protocol.TextDocumentContentChangeEvent{ 66 | { 67 | Range: protocol.Range{ 68 | Start: protocol.Position{Line: 1, Character: 0}, 69 | End: protocol.Position{Line: 1, Character: 0}, 70 | }, 71 | Text: "This should fail", 72 | }, 73 | }, 74 | expectError: true, 75 | }, 76 | } 77 | 78 | for _, tt := range tests { 79 | t.Run(tt.name, func(t *testing.T) { 80 | result, err := applyChangesToDocument(tt.initialDoc, tt.changes) 81 | 82 | if tt.expectError { 83 | if err == nil { 84 | t.Errorf("Expected an error, but got none") 85 | } 86 | } else { 87 | if err != nil { 88 | t.Errorf("Unexpected error: %v", err) 89 | } 90 | if result != tt.expectedResult { 91 | t.Errorf("Expected result %q, but got %q", tt.expectedResult, result) 92 | } 93 | } 94 | }) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /liveconf/liveconf.go: -------------------------------------------------------------------------------- 1 | package liveconf 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | type ConfigLoader func(data []byte, config any) error 12 | 13 | type ConfigWatcher[T any] struct { 14 | filePath string 15 | config *T 16 | mutex sync.RWMutex 17 | lastModTime time.Time 18 | loader ConfigLoader 19 | 20 | errOnNotExist bool 21 | } 22 | 23 | type ConfigOption string 24 | 25 | const ( 26 | ErrOnNotExist ConfigOption = "Error when file does not exist" 27 | ) 28 | 29 | func NewConfigWatcher[T any](filePath, defaultValue string, config *T, loader ConfigLoader, options ...ConfigOption) (*ConfigWatcher[T], error) { 30 | filePath, err := filepath.Abs(filePath) 31 | if err != nil { 32 | return nil, fmt.Errorf("failed to make path absolute: %w", err) 33 | } 34 | 35 | _, err = os.Stat(filePath) 36 | if os.IsNotExist(err) { 37 | err = os.MkdirAll(filepath.Dir(filePath), 0755) 38 | if err != nil { 39 | return nil, err 40 | } 41 | err = os.WriteFile(filePath, []byte(defaultValue), 0755) 42 | if err != nil { 43 | return nil, err 44 | } 45 | } 46 | 47 | cw := &ConfigWatcher[T]{ 48 | filePath: filePath, 49 | config: config, 50 | loader: loader, 51 | } 52 | 53 | for _, opt := range options { 54 | switch opt { 55 | case ErrOnNotExist: 56 | cw.errOnNotExist = true 57 | } 58 | } 59 | 60 | if err := cw.loadConfig(); err != nil { 61 | return nil, fmt.Errorf("failed to load initial config: %w", err) 62 | } 63 | 64 | return cw, nil 65 | } 66 | 67 | func (cw *ConfigWatcher[T]) Set(config T) { 68 | cw.config = &config 69 | } 70 | 71 | func (cw *ConfigWatcher[T]) Get() (T, error) { 72 | cw.mutex.Lock() 73 | defer cw.mutex.Unlock() 74 | 75 | if err := cw.checkAndReload(); err != nil { 76 | return *cw.config, err 77 | } 78 | 79 | return *cw.config, nil 80 | } 81 | 82 | func (cw *ConfigWatcher[T]) checkAndReload() error { 83 | stat, err := os.Stat(cw.filePath) 84 | if os.IsNotExist(err) { 85 | return nil 86 | } 87 | if err != nil { 88 | return fmt.Errorf("failed to stat file: %w", err) 89 | } 90 | 91 | if stat.ModTime() != cw.lastModTime { 92 | if err := cw.loadConfig(); err != nil { 93 | return fmt.Errorf("failed to reload config: %w", err) 94 | } 95 | cw.lastModTime = stat.ModTime() 96 | } 97 | 98 | return nil 99 | } 100 | 101 | func (cw *ConfigWatcher[T]) loadConfig() error { 102 | data, err := os.ReadFile(cw.filePath) 103 | 104 | if err != nil { 105 | if os.IsNotExist(err) && !cw.errOnNotExist { 106 | // There's no configuration 107 | return nil 108 | } 109 | 110 | return fmt.Errorf("failed to read file: %w", err) 111 | } 112 | 113 | if err := cw.loader(data, cw.config); err != nil { 114 | return fmt.Errorf("failed to unmarshal: %w", err) 115 | } 116 | 117 | return nil 118 | } 119 | -------------------------------------------------------------------------------- /cmd/test-ls-files/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | "time" 9 | 10 | "github.com/go-git/go-git/v5" 11 | "github.com/go-git/go-git/v5/plumbing/object" 12 | ) 13 | 14 | func main() { 15 | if len(os.Args) != 2 { 16 | fmt.Println("Usage: go run main.go ") 17 | os.Exit(1) 18 | } 19 | 20 | repoPath := os.Args[1] 21 | runs := 5 22 | 23 | // Benchmark git ls-files shell command 24 | shellTimes := make([]time.Duration, runs) 25 | shellFiles := 0 26 | for i := 0; i < runs; i++ { 27 | start := time.Now() 28 | files, err := gitLsFilesShell(repoPath) 29 | if err != nil { 30 | fmt.Printf("Shell command error: %v\n", err) 31 | os.Exit(1) 32 | } 33 | shellTimes[i] = time.Since(start) 34 | if i == 0 { 35 | shellFiles = len(files) 36 | fmt.Printf("Shell command found %d files\n", shellFiles) 37 | } 38 | } 39 | 40 | // Benchmark go-git tree traversal 41 | goGitTimes := make([]time.Duration, runs) 42 | goGitFiles := 0 43 | for i := 0; i < runs; i++ { 44 | start := time.Now() 45 | files, err := gitLsFilesGoGit(repoPath) 46 | if err != nil { 47 | fmt.Printf("go-git error: %v\n", err) 48 | os.Exit(1) 49 | } 50 | goGitTimes[i] = time.Since(start) 51 | if i == 0 { 52 | goGitFiles = len(files) 53 | fmt.Printf("go-git found %d files\n", goGitFiles) 54 | if goGitFiles != shellFiles { 55 | fmt.Printf("Warning: File count mismatch! Shell: %d, go-git: %d\n", 56 | shellFiles, goGitFiles) 57 | } 58 | } 59 | // Print progress for long-running operations 60 | fmt.Printf("Completed go-git run %d/%d\n", i+1, runs) 61 | } 62 | 63 | // Calculate and print statistics 64 | shellAvg := calculateStats(shellTimes) 65 | goGitAvg := calculateStats(goGitTimes) 66 | 67 | fmt.Printf("\nResults over %d runs:\n", runs) 68 | fmt.Printf("Shell command average: %v (min: %v, max: %v)\n", 69 | shellAvg.avg, shellAvg.min, shellAvg.max) 70 | fmt.Printf("go-git average: %v (min: %v, max: %v)\n", 71 | goGitAvg.avg, goGitAvg.min, goGitAvg.max) 72 | 73 | // Calculate relative performance 74 | ratio := float64(goGitAvg.avg) / float64(shellAvg.avg) 75 | fmt.Printf("\ngo-git is %.2fx %s than shell command\n", 76 | ratio, 77 | map[bool]string{true: "slower", false: "faster"}[ratio > 1]) 78 | } 79 | 80 | type timeStats struct { 81 | avg, min, max time.Duration 82 | } 83 | 84 | func calculateStats(times []time.Duration) timeStats { 85 | var total time.Duration 86 | min := times[0] 87 | max := times[0] 88 | 89 | for _, t := range times { 90 | total += t 91 | if t < min { 92 | min = t 93 | } 94 | if t > max { 95 | max = t 96 | } 97 | } 98 | 99 | return timeStats{ 100 | avg: total / time.Duration(len(times)), 101 | min: min, 102 | max: max, 103 | } 104 | } 105 | 106 | func gitLsFilesShell(repoPath string) ([]string, error) { 107 | cmd := exec.Command("git", "ls-files") 108 | cmd.Dir = repoPath 109 | output, err := cmd.Output() 110 | if err != nil { 111 | return nil, err 112 | } 113 | return strings.Split(strings.TrimSpace(string(output)), "\n"), nil 114 | } 115 | 116 | func gitLsFilesGoGit(repoPath string) ([]string, error) { 117 | repo, err := git.PlainOpen(repoPath) 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | ref, err := repo.Head() 123 | if err != nil { 124 | return nil, fmt.Errorf("error getting HEAD: %w", err) 125 | } 126 | 127 | commit, err := repo.CommitObject(ref.Hash()) 128 | if err != nil { 129 | return nil, fmt.Errorf("error getting commit: %w", err) 130 | } 131 | 132 | tree, err := commit.Tree() 133 | if err != nil { 134 | return nil, fmt.Errorf("error getting tree: %w", err) 135 | } 136 | 137 | var files []string 138 | err = tree.Files().ForEach(func(f *object.File) error { 139 | files = append(files, f.Name) 140 | return nil 141 | }) 142 | if err != nil { 143 | return nil, fmt.Errorf("error walking tree: %w", err) 144 | } 145 | 146 | return files, nil 147 | } 148 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | 11 | "github.com/mattn/go-isatty" 12 | "github.com/rs/zerolog" 13 | "github.com/rs/zerolog/log" 14 | "github.com/spf13/cobra" 15 | "go.lsp.dev/jsonrpc2" 16 | "go.lsp.dev/protocol" 17 | "go.uber.org/zap" 18 | "go.uber.org/zap/zapcore" 19 | ) 20 | 21 | type SymbolInfo struct { 22 | protocol.SymbolInformation 23 | RelativePath string 24 | Description string 25 | Embedding []float64 26 | } 27 | 28 | func main() { 29 | rootCmd := &cobra.Command{ 30 | Use: "sage", 31 | } 32 | 33 | rootCmd.AddCommand(IndexCmd, LanguageServerCmd, CompletionCmd, DevCmds, CtxCmd) 34 | 35 | if isatty.IsTerminal(os.Stdout.Fd()) { 36 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) 37 | } 38 | 39 | err := rootCmd.Execute() 40 | if err != nil { 41 | fmt.Fprintln(os.Stderr, err.Error()) 42 | os.Exit(1) 43 | } 44 | } 45 | 46 | func startLsp(lsp *LanguageServerConfig, clientConn *protocol.Client, params *protocol.InitializeParams) (*ChildLanguageServer, error) { 47 | wd, err := os.Getwd() 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | cmd := exec.Command(*lsp.Command, lsp.Args...) 53 | stdin, err := cmd.StdinPipe() 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | stdout, err := cmd.StdoutPipe() 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | stderr, err := cmd.StderrPipe() 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | workspaceDir := getWorkspaceDir(wd) 69 | stdoutFileName := filepath.Join(workspaceDir, "lsp_stdout.log") 70 | stdoutFile, err := os.Create(stdoutFileName) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | stdinFileName := filepath.Join(workspaceDir, "lsp_stdin.log") 76 | stdinFile, err := os.Create(stdinFileName) 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | stderrFileName := filepath.Join(workspaceDir, "lsp_stderr.log") 82 | stderrFile, err := os.Create(stderrFileName) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | go func() { 88 | stderrScanner := bufio.NewScanner(stderr) 89 | for stderrScanner.Scan() { 90 | _, err := stderrFile.Write(stderrScanner.Bytes()) 91 | if err != nil { 92 | panic(err) 93 | } 94 | stderrFile.Sync() 95 | } 96 | 97 | err := stderrFile.Close() 98 | if err != nil { 99 | panic(err) 100 | } 101 | }() 102 | 103 | if err := cmd.Start(); err != nil { 104 | return nil, err 105 | } 106 | 107 | jsonRpcLogFile := filepath.Join(workspaceDir, "json_rpc.log") 108 | jsonRpcLog, err := os.Create(jsonRpcLogFile) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | encoderConfig := zap.NewProductionEncoderConfig() 114 | encoderConfig.TimeKey = "timestamp" 115 | encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 116 | core := zapcore.NewCore( 117 | zapcore.NewJSONEncoder(encoderConfig), // Encoder configuration 118 | zapcore.AddSync(jsonRpcLog), // Log file 119 | zap.DebugLevel, // Log level 120 | ) 121 | 122 | // Create the logger 123 | logger := zap.New(core) 124 | 125 | rwc := &CombinedReadWriteCloser{ 126 | Reader: stdout, 127 | Writer: stdin, 128 | Closer: func() error { 129 | err2 := stdin.Close() 130 | err1 := stdout.Close() 131 | if err1 != nil || err2 != nil { 132 | return fmt.Errorf("Stdout:(%w) Stdin:(%w)", err1, err2) 133 | } 134 | 135 | return cmd.Process.Kill() 136 | }, 137 | readFile: stdoutFile, 138 | writeFile: stdinFile, 139 | } 140 | stream := jsonrpc2.NewStream(rwc) 141 | conn := jsonrpc2.NewConn(stream) 142 | 143 | if clientConn == nil { 144 | lspClient := protocol.ClientDispatcher(conn, logger) 145 | clientConn = &lspClient 146 | } 147 | 148 | ctx, conn, server := protocol.NewClient(context.Background(), *clientConn, stream, zap.L()) 149 | 150 | result, err := server.Initialize(ctx, params) 151 | if err != nil { 152 | return nil, err 153 | } 154 | 155 | childLs := &ChildLanguageServer{ 156 | Conn: conn, 157 | Cmd: cmd, 158 | Server: server, 159 | Context: ctx, 160 | Close: func() { 161 | logger.Sync() 162 | jsonRpcLog.Close() 163 | stdoutFile.Close() 164 | stdinFile.Close() 165 | stderrFile.Close() 166 | conn.Close() 167 | }, 168 | InitResult: result, 169 | } 170 | 171 | go func() { 172 | cmd.Wait() 173 | }() 174 | 175 | return childLs, nil 176 | } 177 | -------------------------------------------------------------------------------- /dev_cmds.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "io" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | "strings" 12 | "sync" 13 | 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | func init() { 18 | queryFlags := RunQueryCmd.PersistentFlags() 19 | queryFlags.String("query", "", "The value to query the DB for") 20 | 21 | embeddingFlags := GetEmbeddingCmd.PersistentFlags() 22 | embeddingFlags.String("text", "", "The text to embed") 23 | embeddingFlags.String("model", "", "The embedding model to use") 24 | 25 | DevCmds.AddCommand(RunQueryCmd, GetEmbeddingCmd, InspectCmd) 26 | } 27 | 28 | var DevCmds = &cobra.Command{ 29 | Use: "dev", 30 | Short: "Development commands", 31 | } 32 | 33 | var InspectCmd = &cobra.Command{ 34 | Use: "inspect [cmd args...]", 35 | Short: "Log all LSP interactions while acting as a passthrough", 36 | RunE: func(cmd *cobra.Command, args []string) error { 37 | wd, err := os.Getwd() 38 | if err != nil { 39 | return err 40 | } 41 | 42 | inLog := filepath.Join(wd, "lsp-stdin.log") 43 | outLog := filepath.Join(wd, "lsp-stdout.log") 44 | 45 | // Open log files 46 | inFile, err := os.Create(inLog) 47 | if err != nil { 48 | return err 49 | } 50 | defer inFile.Close() 51 | 52 | outFile, err := os.Create(outLog) 53 | if err != nil { 54 | return err 55 | } 56 | defer outFile.Close() 57 | 58 | // Start the language server 59 | cmdSpl := strings.Split(args[0], " ") 60 | lspCmd := exec.Command(cmdSpl[0], cmdSpl[1:]...) 61 | serverIn, err := lspCmd.StdinPipe() 62 | if err != nil { 63 | return err 64 | } 65 | serverOut, err := lspCmd.StdoutPipe() 66 | if err != nil { 67 | return err 68 | } 69 | serverErr, err := lspCmd.StderrPipe() 70 | if err != nil { 71 | return err 72 | } 73 | 74 | if err := lspCmd.Start(); err != nil { 75 | return err 76 | } 77 | 78 | var wg sync.WaitGroup 79 | wg.Add(3) 80 | 81 | // Log and forward stdin 82 | go func() { 83 | defer wg.Done() 84 | multiWriter := io.MultiWriter(serverIn, inFile) 85 | scanner := bufio.NewScanner(os.Stdin) 86 | scanner.Split(bufio.ScanLines) 87 | for scanner.Scan() { 88 | line := scanner.Text() + "\n" 89 | multiWriter.Write([]byte(line)) 90 | } 91 | if err := scanner.Err(); err != nil { 92 | println("Error reading from stdin:", err.Error()) 93 | } 94 | serverIn.Close() 95 | }() 96 | 97 | // Log and forward stdout 98 | go func() { 99 | defer wg.Done() 100 | multiWriter := io.MultiWriter(os.Stdout, outFile) 101 | scanner := bufio.NewScanner(serverOut) 102 | scanner.Split(bufio.ScanLines) 103 | for scanner.Scan() { 104 | line := scanner.Text() + "\n" 105 | multiWriter.Write([]byte(line)) 106 | } 107 | if err := scanner.Err(); err != nil { 108 | println("Error reading from stdout:", err.Error()) 109 | } 110 | }() 111 | 112 | // Log stderr (if any) 113 | go func() { 114 | defer wg.Done() 115 | scanner := bufio.NewScanner(serverErr) 116 | scanner.Split(bufio.ScanLines) 117 | for scanner.Scan() { 118 | line := scanner.Text() + "\n" 119 | outFile.Write([]byte(line)) 120 | } 121 | if err := scanner.Err(); err != nil { 122 | println("Error reading from stderr:", err.Error()) 123 | } 124 | }() 125 | 126 | // Wait for the language server to finish 127 | if err := lspCmd.Wait(); err != nil { 128 | return err 129 | } 130 | 131 | wg.Wait() 132 | return nil 133 | }, 134 | } 135 | 136 | var GetEmbeddingCmd = &cobra.Command{ 137 | Use: "embedding", 138 | RunE: func(cmd *cobra.Command, args []string) error { 139 | flags := cmd.Flags() 140 | 141 | model, err := flags.GetString("model") 142 | if err != nil { 143 | return err 144 | } 145 | 146 | text, err := flags.GetString("text") 147 | if err != nil { 148 | return err 149 | } 150 | 151 | llm, err := NewLLMClient() 152 | if err != nil { 153 | return err 154 | } 155 | 156 | embedding, err := llm.GetEmbedding(context.TODO(), model, text) 157 | if err != nil { 158 | return err 159 | } 160 | 161 | var vecStrs []string 162 | for _, f := range embedding { 163 | vecStrs = append(vecStrs, fmt.Sprint(float32(f))) 164 | } 165 | 166 | fmt.Println("[" + strings.Join(vecStrs, ", ") + "]") 167 | fmt.Fprintln(os.Stderr, len(vecStrs), "dimensions") 168 | return nil 169 | }, 170 | } 171 | 172 | var RunQueryCmd = &cobra.Command{ 173 | Use: "query-symbols", 174 | RunE: func(cmd *cobra.Command, args []string) error { 175 | flags := cmd.Flags() 176 | 177 | query, err := flags.GetString("query") 178 | if err != nil { 179 | return err 180 | } 181 | 182 | wd, err := os.Getwd() 183 | if err != nil { 184 | return err 185 | } 186 | 187 | db, err := openDB(wd) 188 | if err != nil { 189 | return err 190 | } 191 | 192 | llm, err := NewLLMClient() 193 | if err != nil { 194 | return err 195 | } 196 | 197 | symbols, err := findSymbol(context.TODO(), db, llm, query) 198 | if err != nil { 199 | return err 200 | } 201 | 202 | for _, sym := range symbols { 203 | fmt.Println(sym.Name, sym.Location) 204 | } 205 | 206 | return nil 207 | }, 208 | } 209 | -------------------------------------------------------------------------------- /rpc/languageserver/LanguageServerState_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.5.1 4 | // - protoc v5.28.2 5 | // source: rpc/languageserver/LanguageServerState.proto 6 | 7 | package languageserver 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.64.0 or later. 19 | const _ = grpc.SupportPackageIsVersion9 20 | 21 | const ( 22 | LanguageServerState_GetOpenDocuments_FullMethodName = "/languageserver.LanguageServerState/GetOpenDocuments" 23 | ) 24 | 25 | // LanguageServerStateClient is the client API for LanguageServerState service. 26 | // 27 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 28 | type LanguageServerStateClient interface { 29 | GetOpenDocuments(ctx context.Context, in *GetOpenDocumentsRequest, opts ...grpc.CallOption) (*GetOpenDocumentsResponse, error) 30 | } 31 | 32 | type languageServerStateClient struct { 33 | cc grpc.ClientConnInterface 34 | } 35 | 36 | func NewLanguageServerStateClient(cc grpc.ClientConnInterface) LanguageServerStateClient { 37 | return &languageServerStateClient{cc} 38 | } 39 | 40 | func (c *languageServerStateClient) GetOpenDocuments(ctx context.Context, in *GetOpenDocumentsRequest, opts ...grpc.CallOption) (*GetOpenDocumentsResponse, error) { 41 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 42 | out := new(GetOpenDocumentsResponse) 43 | err := c.cc.Invoke(ctx, LanguageServerState_GetOpenDocuments_FullMethodName, in, out, cOpts...) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return out, nil 48 | } 49 | 50 | // LanguageServerStateServer is the server API for LanguageServerState service. 51 | // All implementations must embed UnimplementedLanguageServerStateServer 52 | // for forward compatibility. 53 | type LanguageServerStateServer interface { 54 | GetOpenDocuments(context.Context, *GetOpenDocumentsRequest) (*GetOpenDocumentsResponse, error) 55 | mustEmbedUnimplementedLanguageServerStateServer() 56 | } 57 | 58 | // UnimplementedLanguageServerStateServer must be embedded to have 59 | // forward compatible implementations. 60 | // 61 | // NOTE: this should be embedded by value instead of pointer to avoid a nil 62 | // pointer dereference when methods are called. 63 | type UnimplementedLanguageServerStateServer struct{} 64 | 65 | func (UnimplementedLanguageServerStateServer) GetOpenDocuments(context.Context, *GetOpenDocumentsRequest) (*GetOpenDocumentsResponse, error) { 66 | return nil, status.Errorf(codes.Unimplemented, "method GetOpenDocuments not implemented") 67 | } 68 | func (UnimplementedLanguageServerStateServer) mustEmbedUnimplementedLanguageServerStateServer() {} 69 | func (UnimplementedLanguageServerStateServer) testEmbeddedByValue() {} 70 | 71 | // UnsafeLanguageServerStateServer may be embedded to opt out of forward compatibility for this service. 72 | // Use of this interface is not recommended, as added methods to LanguageServerStateServer will 73 | // result in compilation errors. 74 | type UnsafeLanguageServerStateServer interface { 75 | mustEmbedUnimplementedLanguageServerStateServer() 76 | } 77 | 78 | func RegisterLanguageServerStateServer(s grpc.ServiceRegistrar, srv LanguageServerStateServer) { 79 | // If the following call pancis, it indicates UnimplementedLanguageServerStateServer was 80 | // embedded by pointer and is nil. This will cause panics if an 81 | // unimplemented method is ever invoked, so we test this at initialization 82 | // time to prevent it from happening at runtime later due to I/O. 83 | if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { 84 | t.testEmbeddedByValue() 85 | } 86 | s.RegisterService(&LanguageServerState_ServiceDesc, srv) 87 | } 88 | 89 | func _LanguageServerState_GetOpenDocuments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 90 | in := new(GetOpenDocumentsRequest) 91 | if err := dec(in); err != nil { 92 | return nil, err 93 | } 94 | if interceptor == nil { 95 | return srv.(LanguageServerStateServer).GetOpenDocuments(ctx, in) 96 | } 97 | info := &grpc.UnaryServerInfo{ 98 | Server: srv, 99 | FullMethod: LanguageServerState_GetOpenDocuments_FullMethodName, 100 | } 101 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 102 | return srv.(LanguageServerStateServer).GetOpenDocuments(ctx, req.(*GetOpenDocumentsRequest)) 103 | } 104 | return interceptor(ctx, in, info, handler) 105 | } 106 | 107 | // LanguageServerState_ServiceDesc is the grpc.ServiceDesc for LanguageServerState service. 108 | // It's only intended for direct use with grpc.RegisterService, 109 | // and not to be introspected or modified (even as a copy) 110 | var LanguageServerState_ServiceDesc = grpc.ServiceDesc{ 111 | ServiceName: "languageserver.LanguageServerState", 112 | HandlerType: (*LanguageServerStateServer)(nil), 113 | Methods: []grpc.MethodDesc{ 114 | { 115 | MethodName: "GetOpenDocuments", 116 | Handler: _LanguageServerState_GetOpenDocuments_Handler, 117 | }, 118 | }, 119 | Streams: []grpc.StreamDesc{}, 120 | Metadata: "rpc/languageserver/LanguageServerState.proto", 121 | } 122 | -------------------------------------------------------------------------------- /llm_context_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestParseContext(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | input string 12 | want []*ContextItemProvider 13 | wantErr bool 14 | }{ 15 | { 16 | name: "Single file", 17 | input: "file.go", 18 | want: []*ContextItemProvider{ 19 | { 20 | parts: []string{"file.go"}, 21 | itemType: ContextItemTypeFile, 22 | }, 23 | }, 24 | wantErr: false, 25 | }, 26 | { 27 | name: "File with symbol", 28 | input: "file.go MyFunction", 29 | want: []*ContextItemProvider{ 30 | { 31 | parts: []string{"file.go", "MyFunction"}, 32 | itemType: ContextItemTypeSymbol, 33 | }, 34 | }, 35 | wantErr: false, 36 | }, 37 | { 38 | name: "File with range", 39 | input: "file.go 10:20", 40 | want: []*ContextItemProvider{ 41 | { 42 | parts: []string{"file.go", "10:20"}, 43 | itemType: ContextItemTypeRange, 44 | }, 45 | }, 46 | wantErr: false, 47 | }, 48 | { 49 | name: "File with multiple items", 50 | input: "file.go MyFunction 10:20 AnotherSymbol", 51 | want: []*ContextItemProvider{ 52 | { 53 | parts: []string{"file.go", "MyFunction", "10:20", "AnotherSymbol"}, 54 | itemType: ContextItemTypeSymbol, 55 | }, 56 | { 57 | parts: []string{"file.go", "MyFunction", "10:20", "AnotherSymbol"}, 58 | itemType: ContextItemTypeRange, 59 | }, 60 | { 61 | parts: []string{"file.go", "MyFunction", "10:20", "AnotherSymbol"}, 62 | itemType: ContextItemTypeSymbol, 63 | }, 64 | }, 65 | wantErr: false, 66 | }, 67 | { 68 | name: "Multiple lines", 69 | input: "file1.go MyFunction\nfile2.go 10:20", 70 | want: []*ContextItemProvider{ 71 | { 72 | parts: []string{"file1.go", "MyFunction"}, 73 | itemType: ContextItemTypeSymbol, 74 | }, 75 | { 76 | parts: []string{"file2.go", "10:20"}, 77 | itemType: ContextItemTypeRange, 78 | }, 79 | }, 80 | wantErr: false, 81 | }, 82 | { 83 | name: "Invalid range", 84 | input: "file.go 20:10", 85 | want: nil, 86 | wantErr: true, 87 | }, 88 | { 89 | name: "Quoted filename with spaces", 90 | input: `"my file.go" MyFunction`, 91 | want: []*ContextItemProvider{ 92 | { 93 | parts: []string{"my file.go", "MyFunction"}, 94 | itemType: ContextItemTypeSymbol, 95 | }, 96 | }, 97 | wantErr: false, 98 | }, 99 | { 100 | name: "Unclosed quote", 101 | input: `"my file.go MyFunction`, 102 | want: nil, 103 | wantErr: true, 104 | }, 105 | } 106 | 107 | for _, tt := range tests { 108 | t.Run(tt.name, func(t *testing.T) { 109 | got, err := ParseContext(tt.input) 110 | if (err != nil) != tt.wantErr { 111 | t.Errorf("ParseContext() error = %v, wantErr %v", err, tt.wantErr) 112 | return 113 | } 114 | for i, gotItem := range got { 115 | if i >= len(tt.want) { 116 | t.Errorf("ParseContext() = %v, want %v", got, tt.want) 117 | } 118 | wantItem := tt.want[i] 119 | 120 | if gotItem.itemType != wantItem.itemType { 121 | t.Errorf("Got item %v, want %v", gotItem, wantItem) 122 | } 123 | 124 | if len(gotItem.parts) != len(wantItem.parts) { 125 | t.Errorf("Got item %v, want %v", gotItem, wantItem) 126 | } 127 | 128 | for p, gotPart := range gotItem.parts { 129 | if gotPart != wantItem.parts[p] { 130 | t.Errorf("Got item %v, want %v", gotItem, wantItem) 131 | } 132 | } 133 | } 134 | }) 135 | } 136 | } 137 | 138 | func TestContextItemProvider_GetItem(t *testing.T) { 139 | // Mock ContextApi implementation 140 | mockApi := &MockContextApi{} 141 | 142 | tests := []struct { 143 | name string 144 | provider *ContextItemProvider 145 | want *ContextItem 146 | wantErr bool 147 | }{ 148 | { 149 | name: "Get file", 150 | provider: &ContextItemProvider{ 151 | parts: []string{"file.go"}, 152 | itemType: ContextItemTypeFile, 153 | getter: func(ca ContextApi) (*ContextItem, error) { 154 | content, _ := ca.GetFile("file.go") 155 | return &ContextItem{Filename: "file.go", Content: content}, nil 156 | }, 157 | }, 158 | want: &ContextItem{Filename: "file.go", Content: "file content"}, 159 | wantErr: false, 160 | }, 161 | { 162 | name: "Get symbol", 163 | provider: &ContextItemProvider{ 164 | parts: []string{"file.go", "MyFunction"}, 165 | itemType: ContextItemTypeSymbol, 166 | getter: func(ca ContextApi) (*ContextItem, error) { 167 | content, _ := ca.GetSymbol("file.go", "MyFunction") 168 | return &ContextItem{Filename: "file.go", Identifier: "MyFunction", Content: content}, nil 169 | }, 170 | }, 171 | want: &ContextItem{Filename: "file.go", Identifier: "MyFunction", Content: "function content"}, 172 | wantErr: false, 173 | }, 174 | { 175 | name: "Get range", 176 | provider: &ContextItemProvider{ 177 | parts: []string{"file.go", "10:20"}, 178 | itemType: ContextItemTypeRange, 179 | getter: func(ca ContextApi) (*ContextItem, error) { 180 | content, _ := ca.GetRange("file.go", 10, 20) 181 | return &ContextItem{Filename: "file.go", Identifier: "10:20", Content: content}, nil 182 | }, 183 | }, 184 | want: &ContextItem{Filename: "file.go", Identifier: "10:20", Content: "range content"}, 185 | wantErr: false, 186 | }, 187 | } 188 | 189 | for _, tt := range tests { 190 | t.Run(tt.name, func(t *testing.T) { 191 | got, err := tt.provider.GetItem(mockApi) 192 | if (err != nil) != tt.wantErr { 193 | t.Errorf("ContextItemProvider.GetItem() error = %v, wantErr %v", err, tt.wantErr) 194 | return 195 | } 196 | if !reflect.DeepEqual(got, tt.want) { 197 | t.Errorf("ContextItemProvider.GetItem() = %v, want %v", got, tt.want) 198 | } 199 | }) 200 | } 201 | } 202 | 203 | // MockContextApi is a mock implementation of ContextApi for testing 204 | type MockContextApi struct{} 205 | 206 | func (m *MockContextApi) GetSymbol(filename, symbolName string) (string, error) { 207 | return "function content", nil 208 | } 209 | 210 | func (m *MockContextApi) GetRange(filename string, start, end int) (string, error) { 211 | return "range content", nil 212 | } 213 | 214 | func (m *MockContextApi) GetFile(filename string) (string, error) { 215 | return "file content", nil 216 | } 217 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/everestmz/sage 2 | 3 | go 1.22.7 4 | 5 | toolchain go1.22.9 6 | 7 | require github.com/rs/zerolog v1.33.0 8 | 9 | require ( 10 | connectrpc.com/connect v1.17.0 // indirect 11 | dario.cat/mergo v1.0.0 // indirect 12 | github.com/BurntSushi/toml v1.2.1 // indirect 13 | github.com/Microsoft/go-winio v0.6.1 // indirect 14 | github.com/ProtonMail/go-crypto v1.0.0 // indirect 15 | github.com/agnivade/levenshtein v1.1.1 // indirect 16 | github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 // indirect 17 | github.com/asg017/sqlite-vec/bindings/go/cgo v0.0.0-20240628232741-344213161399 // indirect 18 | github.com/asg017/sqlite-vss/bindings/go v0.0.0-20240505200016-8d3c6ff2fca6 // indirect 19 | github.com/bytedance/sonic v1.11.6 // indirect 20 | github.com/bytedance/sonic/loader v0.1.1 // indirect 21 | github.com/chewxy/hm v1.0.0 // indirect 22 | github.com/chewxy/math32 v1.10.1 // indirect 23 | github.com/cloudflare/circl v1.3.7 // indirect 24 | github.com/cloudwego/base64x v0.1.4 // indirect 25 | github.com/cloudwego/iasm v0.2.0 // indirect 26 | github.com/coder/websocket v1.8.12 // indirect 27 | github.com/containerd/console v1.0.3 // indirect 28 | github.com/cyphar/filepath-securejoin v0.2.4 // indirect 29 | github.com/d4l3k/go-bfloat16 v0.0.0-20211005043715-690c3bdd05f1 // indirect 30 | github.com/emirpasic/gods v1.18.1 // indirect 31 | github.com/everestmz/cursor-rpc v0.0.0-20241202041540-8dd67a7b9804 // indirect 32 | github.com/everestmz/llmcat v0.0.5 // indirect 33 | github.com/fatih/color v1.17.0 // indirect 34 | github.com/fsnotify/fsnotify v1.7.0 // indirect 35 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 36 | github.com/gin-contrib/cors v1.7.2 // indirect 37 | github.com/gin-contrib/sse v0.1.0 // indirect 38 | github.com/gin-gonic/gin v1.10.0 // indirect 39 | github.com/glycerine/blake2b v0.0.0-20151022103502-3c8c640cd7be // indirect 40 | github.com/glycerine/greenpack v5.1.1+incompatible // indirect 41 | github.com/glycerine/liner v0.0.0-20160121172638-72909af234e0 // indirect 42 | github.com/glycerine/zygomys v5.1.2+incompatible // indirect 43 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 44 | github.com/go-git/go-billy/v5 v5.5.0 // indirect 45 | github.com/go-git/go-git/v5 v5.12.0 // indirect 46 | github.com/go-playground/locales v0.14.1 // indirect 47 | github.com/go-playground/universal-translator v0.18.1 // indirect 48 | github.com/go-playground/validator/v10 v10.20.0 // indirect 49 | github.com/gobwas/glob v0.2.3 // indirect 50 | github.com/goccy/go-json v0.10.2 // indirect 51 | github.com/goccy/go-yaml v1.11.3 // indirect 52 | github.com/gogo/protobuf v1.3.2 // indirect 53 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 54 | github.com/golang/protobuf v1.5.4 // indirect 55 | github.com/google/flatbuffers v24.3.25+incompatible // indirect 56 | github.com/google/go-cmp v0.6.0 // indirect 57 | github.com/google/uuid v1.6.0 // indirect 58 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 59 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 60 | github.com/json-iterator/go v1.1.12 // indirect 61 | github.com/kevinburke/ssh_config v1.2.0 // indirect 62 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect 63 | github.com/leodido/go-urn v1.4.0 // indirect 64 | github.com/mattn/go-colorable v0.1.13 // indirect 65 | github.com/mattn/go-isatty v0.0.20 // indirect 66 | github.com/mattn/go-runewidth v0.0.14 // indirect 67 | github.com/mattn/go-sqlite3 v1.14.22 // indirect 68 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 69 | github.com/modern-go/reflect2 v1.0.2 // indirect 70 | github.com/nlpodyssey/gopickle v0.3.0 // indirect 71 | github.com/olekukonko/tablewriter v0.0.5 // indirect 72 | github.com/ollama/ollama v0.1.46 // indirect 73 | github.com/pdevine/tensor v0.0.0-20240510204454-f88f4562727c // indirect 74 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 75 | github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 // indirect 76 | github.com/pjbgf/sha1cd v0.3.0 // indirect 77 | github.com/pkg/errors v0.9.1 // indirect 78 | github.com/rivo/uniseg v0.2.0 // indirect 79 | github.com/segmentio/asm v1.1.3 // indirect 80 | github.com/segmentio/encoding v0.3.4 // indirect 81 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect 82 | github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 // indirect 83 | github.com/shurcooL/go-goon v1.0.0 // indirect 84 | github.com/skeema/knownhosts v1.2.2 // indirect 85 | github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 // indirect 86 | github.com/spf13/cobra v1.8.1 // indirect 87 | github.com/spf13/pflag v1.0.5 // indirect 88 | github.com/tinylib/msgp v1.2.0 // indirect 89 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 90 | github.com/ugorji/go/codec v1.2.12 // indirect 91 | github.com/x448/float16 v0.8.4 // indirect 92 | github.com/xanzy/ssh-agent v0.3.3 // indirect 93 | github.com/xtgo/set v1.0.0 // indirect 94 | go.lsp.dev/jsonrpc2 v0.10.0 // indirect 95 | go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect 96 | go.lsp.dev/protocol v0.12.0 // indirect 97 | go.lsp.dev/uri v0.3.0 // indirect 98 | go.uber.org/atomic v1.11.0 // indirect 99 | go.uber.org/multierr v1.11.0 // indirect 100 | go.uber.org/zap v1.24.0 // indirect 101 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect 102 | golang.org/x/arch v0.8.0 // indirect 103 | golang.org/x/crypto v0.27.0 // indirect 104 | golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect 105 | golang.org/x/exp/typeparams v0.0.0-20221212164502-fae10dda9338 // indirect 106 | golang.org/x/mod v0.18.0 // indirect 107 | golang.org/x/net v0.29.0 // indirect 108 | golang.org/x/sync v0.8.0 // indirect 109 | golang.org/x/sys v0.27.0 // indirect 110 | golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 // indirect 111 | golang.org/x/term v0.24.0 // indirect 112 | golang.org/x/text v0.18.0 // indirect 113 | golang.org/x/tools v0.22.0 // indirect 114 | golang.org/x/tools/gopls v0.15.3 // indirect 115 | golang.org/x/vuln v1.0.1 // indirect 116 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect 117 | gonum.org/v1/gonum v0.15.0 // indirect 118 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect 119 | google.golang.org/grpc v1.68.0 // indirect 120 | google.golang.org/protobuf v1.34.2 // indirect 121 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect 122 | gopkg.in/warnings.v0 v0.1.2 // indirect 123 | gopkg.in/yaml.v3 v3.0.1 // indirect 124 | gorgonia.org/vecf32 v0.9.0 // indirect 125 | gorgonia.org/vecf64 v0.9.0 // indirect 126 | honnef.co/go/tools v0.4.6 // indirect 127 | mvdan.cc/gofumpt v0.6.0 // indirect 128 | mvdan.cc/xurls/v2 v2.5.0 // indirect 129 | ) 130 | -------------------------------------------------------------------------------- /llm_context.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type ContextItemGetterFunc func(ContextApi) (*ContextItem, error) 11 | 12 | type ContextItemType string 13 | 14 | const ( 15 | ContextItemTypeFile ContextItemType = "file" 16 | ContextItemTypeSymbol ContextItemType = "symbol" 17 | ContextItemTypeRange ContextItemType = "range" 18 | ) 19 | 20 | type ContextApi interface { 21 | GetSymbol(filename string, symbolName string) (string, error) 22 | GetRange(filename string, start, end int) (string, error) 23 | GetFile(filename string) (string, error) 24 | } 25 | 26 | type ContextItem struct { 27 | Filename string 28 | Identifier string // Line number range or symbol name 29 | Content string 30 | } 31 | 32 | type ContextItemProvider struct { 33 | parts []string 34 | itemType ContextItemType 35 | getter ContextItemGetterFunc 36 | } 37 | 38 | func (cip *ContextItemProvider) Type() ContextItemType { 39 | return cip.itemType 40 | } 41 | 42 | func (cip *ContextItemProvider) String() string { 43 | return strings.Join(cip.parts, " ") 44 | } 45 | 46 | func (cip *ContextItemProvider) GetItem(api ContextApi) (*ContextItem, error) { 47 | return cip.getter(api) 48 | } 49 | 50 | func ParseContext(contextDefinition string) ([]*ContextItemProvider, error) { 51 | scanner := bufio.NewScanner(strings.NewReader(contextDefinition)) 52 | scanner.Split(bufio.ScanLines) 53 | 54 | var providers []*ContextItemProvider 55 | 56 | for scanner.Scan() { 57 | line := scanner.Text() 58 | newProviders, err := parseContextLine(line) 59 | if err != nil { 60 | return nil, fmt.Errorf("Error for line '%s': %w", line, err) 61 | } 62 | 63 | providers = append(providers, newProviders...) 64 | } 65 | 66 | return providers, nil 67 | } 68 | 69 | func getLineParts(line string) ([]string, error) { 70 | var parts []string 71 | 72 | var currentPart strings.Builder 73 | 74 | var inQuotes bool 75 | var escaped bool 76 | 77 | for _, char := range line { 78 | switch { 79 | case escaped: 80 | currentPart.WriteRune(char) 81 | escaped = false 82 | case char == '\\': 83 | escaped = true 84 | case char == '"' && !escaped: 85 | inQuotes = !inQuotes 86 | 87 | if !inQuotes { 88 | parts = append(parts, currentPart.String()) 89 | currentPart.Reset() 90 | } 91 | 92 | case char == ' ' && !inQuotes: 93 | if currentPart.Len() > 0 { 94 | parts = append(parts, currentPart.String()) 95 | currentPart.Reset() 96 | } 97 | default: 98 | currentPart.WriteRune(char) 99 | } 100 | } 101 | 102 | if currentPart.Len() > 0 { 103 | parts = append(parts, currentPart.String()) 104 | } 105 | 106 | if inQuotes { 107 | return nil, fmt.Errorf("Found quote with no matching closing quote") 108 | } 109 | 110 | return parts, nil 111 | } 112 | 113 | func parseRange(rangeStr string) (start, end int, err error) { 114 | parts := strings.Split(rangeStr, ":") 115 | if len(parts) != 2 { 116 | return 0, 0, fmt.Errorf("Found range with >2 parts: '%s'", rangeStr) 117 | } 118 | 119 | start, err = strconv.Atoi(parts[0]) 120 | if err != nil { 121 | return 0, 0, fmt.Errorf("Invalid start of range '%s': %w", parts[0], err) 122 | } 123 | 124 | end, err = strconv.Atoi(parts[1]) 125 | if err != nil { 126 | return 0, 0, fmt.Errorf("Invalid end of range '%s': %w", parts[1], err) 127 | } 128 | 129 | if start > end { 130 | return 0, 0, fmt.Errorf("Start cannot be greater than end of range '%s'", rangeStr) 131 | } 132 | 133 | return 134 | } 135 | 136 | func parseContextLine(line string) ([]*ContextItemProvider, error) { 137 | if line == "" { 138 | return nil, nil 139 | } 140 | 141 | parts, err := getLineParts(line) 142 | if err != nil { 143 | return nil, err 144 | } 145 | 146 | var providers []*ContextItemProvider 147 | 148 | filename := parts[0] 149 | 150 | // Our options right now are a whole file, a file range, or a symbol. 151 | // Each row can have one filename, but multiple options for symbols or line ranges 152 | if len(parts) == 1 { 153 | providers = append(providers, &ContextItemProvider{ 154 | parts: parts, 155 | itemType: ContextItemTypeFile, 156 | getter: func(ca ContextApi) (*ContextItem, error) { 157 | content, err := ca.GetFile(filename) 158 | if err != nil { 159 | return nil, err 160 | } 161 | 162 | return &ContextItem{ 163 | Filename: filename, 164 | Identifier: "", 165 | Content: content, 166 | }, nil 167 | }, 168 | }) 169 | 170 | return providers, nil 171 | } 172 | 173 | // We have more than one item for this file 174 | 175 | for _, item := range parts[1:] { 176 | itemType := ContextItemTypeSymbol 177 | var getItem ContextItemGetterFunc = func(ca ContextApi) (*ContextItem, error) { 178 | content, err := ca.GetSymbol(filename, item) 179 | if err != nil { 180 | return nil, err 181 | } 182 | 183 | return &ContextItem{ 184 | Filename: filename, 185 | Identifier: item, 186 | Content: content, 187 | }, nil 188 | } 189 | 190 | if strings.Contains(item, ":") { 191 | itemType = ContextItemTypeRange 192 | start, end, err := parseRange(item) 193 | if err != nil { 194 | return nil, err 195 | } 196 | 197 | getItem = func(ca ContextApi) (*ContextItem, error) { 198 | content, err := ca.GetRange(filename, start, end) 199 | if err != nil { 200 | return nil, err 201 | } 202 | 203 | return &ContextItem{ 204 | Filename: filename, 205 | Identifier: fmt.Sprintf("%d:%d", start, end), 206 | Content: content, 207 | }, nil 208 | } 209 | } 210 | 211 | providers = append(providers, &ContextItemProvider{ 212 | parts: parts, 213 | itemType: itemType, 214 | getter: getItem, 215 | }) 216 | } 217 | 218 | return providers, nil 219 | } 220 | 221 | func BuildContext(providers []*ContextItemProvider, api ContextApi) (string, error) { 222 | var builder strings.Builder 223 | 224 | for _, provider := range providers { 225 | var tagName string 226 | 227 | contextItem, err := provider.GetItem(api) 228 | if err != nil { 229 | return "", err 230 | } 231 | 232 | tagParams := map[string]string{ 233 | "file": contextItem.Filename, 234 | } 235 | 236 | switch provider.Type() { 237 | case ContextItemTypeFile: 238 | tagName = "File" 239 | case ContextItemTypeRange: 240 | tagName = "FileRange" 241 | tagParams["range"] = contextItem.Identifier 242 | case ContextItemTypeSymbol: 243 | tagName = "FileSymbol" 244 | tagParams["symbol"] = contextItem.Identifier 245 | default: 246 | return "", fmt.Errorf("Invalid item type '%s'", provider.Type()) 247 | } 248 | 249 | builder.WriteString("<" + tagName + "\n") 250 | for k, v := range tagParams { 251 | _, err = builder.WriteString(fmt.Sprintf("%s=\"%s\"\n", k, v)) 252 | if err != nil { 253 | return "", err 254 | } 255 | } 256 | builder.WriteString(">\n") 257 | builder.WriteString(contextItem.Content) 258 | builder.WriteString("\n") 259 | builder.WriteString("\n") 260 | } 261 | 262 | return builder.String(), nil 263 | } 264 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/everestmz/sage/liveconf" 9 | "github.com/gobwas/glob" 10 | "gopkg.in/yaml.v3" 11 | ) 12 | 13 | type SageLanguageConfig struct { 14 | Extensions []string `yaml:"extensions"` 15 | LanguageServer *LanguageServerConfig `yaml:"language_server"` 16 | } 17 | 18 | type SageModelsConfig struct { 19 | Embedding *string `yaml:"embedding,omitempty"` 20 | Default *string `yaml:"default,omitempty"` 21 | ExplainCode *string `yaml:"explain_code,omitempty"` 22 | } 23 | 24 | func NewPathConfig(name string) *SagePathConfig { 25 | return &SagePathConfig{ 26 | Path: nil, 27 | name: name, 28 | } 29 | } 30 | 31 | type SagePathConfig struct { 32 | Path *string `yaml:"path"` 33 | Include []string `yaml:"include"` 34 | Exclude []string `yaml:"exclude"` 35 | Models *liveconf.ConfigWatcher[SageModelsConfig] 36 | Context *liveconf.ConfigWatcher[[]*ContextItemProvider] 37 | 38 | compiledIncludes []glob.Glob 39 | compiledExcludes []glob.Glob 40 | name string 41 | } 42 | 43 | func (sc SagePathConfig) Name() string { 44 | return sc.name 45 | } 46 | 47 | func (sc *SagePathConfig) InitDefaults() error { 48 | if sc.Path != nil { 49 | expandedPath := os.ExpandEnv(*sc.Path) 50 | if !filepath.IsAbs(expandedPath) { 51 | return fmt.Errorf("filepath '%s' for config '%s' is not absolute", expandedPath, sc.name) 52 | } 53 | *sc.Path = expandedPath 54 | } 55 | 56 | for _, pattern := range sc.Exclude { 57 | compiled, err := glob.Compile(pattern) 58 | if err != nil { 59 | return fmt.Errorf("'%s.exclude': '%s' is invalid: %w", sc.name, pattern, err) 60 | } 61 | sc.compiledExcludes = append(sc.compiledExcludes, compiled) 62 | } 63 | 64 | for _, pattern := range sc.Include { 65 | compiled, err := glob.Compile(pattern) 66 | if err != nil { 67 | return fmt.Errorf("'%s.include': '%s' is invalid: %w", sc.name, pattern, err) 68 | } 69 | sc.compiledIncludes = append(sc.compiledIncludes, compiled) 70 | } 71 | 72 | modelsConfig := SageModelsConfig{} 73 | defaultModelsConfig, err := yaml.Marshal(SageModelsConfig{ 74 | Default: &DefaultModel, 75 | ExplainCode: &DefaultExplainCodeModel, 76 | Embedding: &DefaultEmbeddingModel, 77 | }) 78 | if err != nil { 79 | return err 80 | } 81 | sc.Models, err = liveconf.NewConfigWatcher[SageModelsConfig](getWorkspaceModelsPath(), string(defaultModelsConfig), &modelsConfig, yaml.Unmarshal) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | providers := []*ContextItemProvider{} 87 | sc.Context, err = liveconf.NewConfigWatcher[[]*ContextItemProvider](getWorkspaceContextPath(), "", &providers, func(data []byte, config any) error { 88 | providersPtr := config.(*[]*ContextItemProvider) 89 | providers, err := ParseContext(string(data)) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | *providersPtr = providers 95 | return nil 96 | }) 97 | if err != nil { 98 | return err 99 | } 100 | 101 | modelsConfig, err = sc.Models.Get() 102 | if err != nil { 103 | return err 104 | } 105 | 106 | if modelsConfig.Default == nil { 107 | modelsConfig.Default = &DefaultModel 108 | } 109 | 110 | if modelsConfig.Embedding == nil { 111 | modelsConfig.Embedding = &DefaultEmbeddingModel 112 | } 113 | 114 | if modelsConfig.ExplainCode == nil { 115 | modelsConfig.ExplainCode = &DefaultExplainCodeModel 116 | } 117 | 118 | sc.Models.Set(modelsConfig) 119 | 120 | return nil 121 | } 122 | 123 | type SageConfig map[string]*SagePathConfig 124 | 125 | func (sc SageConfig) GetConfigForDir(wd string) *SagePathConfig { 126 | baseName := filepath.Base(wd) 127 | 128 | var tentativeConfig *SagePathConfig 129 | 130 | for name, config := range sc { 131 | if name == baseName { 132 | tentativeConfig = config 133 | } 134 | 135 | if config.Path != nil && *config.Path == wd { 136 | return config 137 | } 138 | } 139 | 140 | // Could be nil still 141 | return tentativeConfig 142 | } 143 | 144 | var ( 145 | DefaultModel = "llama3.1:8b" 146 | DefaultEmbeddingModel = "nomic-embed-text" 147 | DefaultExplainCodeModel = "starcoder2:3b" 148 | ) 149 | 150 | func getConfigFromFile(path string) (SageConfig, error) { 151 | bs, err := os.ReadFile(path) 152 | if err != nil { 153 | return nil, err 154 | } 155 | 156 | config := SageConfig{} 157 | 158 | err = yaml.Unmarshal(bs, &config) 159 | if err != nil { 160 | return nil, err 161 | } 162 | 163 | for name, config := range config { 164 | config.name = name 165 | 166 | err = config.InitDefaults() 167 | if err != nil { 168 | return nil, err 169 | } 170 | } 171 | 172 | return config, nil 173 | } 174 | 175 | func getWorkspaceDir(projectDir string) string { 176 | wsDir := filepath.Join(getConfigDir(), "workspaces", projectDir) 177 | err := os.MkdirAll(wsDir, 0755) 178 | if err != nil { 179 | panic(err) 180 | } 181 | 182 | return wsDir 183 | } 184 | 185 | func getConfigDir() string { 186 | home, err := os.UserHomeDir() 187 | if err != nil { 188 | panic(err) 189 | } 190 | 191 | path := filepath.Join(home, ".config", "sage") 192 | 193 | err = os.MkdirAll(path, 0755) 194 | if err != nil { 195 | panic(err) 196 | } 197 | 198 | return path 199 | } 200 | 201 | func getDbsDir() string { 202 | return filepath.Join(getConfigDir(), "dbs") 203 | } 204 | 205 | func getConfigFile() (SageConfig, error) { 206 | configDir := getConfigDir() 207 | 208 | wd, err := os.Getwd() 209 | if err != nil { 210 | panic(err) 211 | } 212 | 213 | wdPath := filepath.Join(wd, "sage.yaml") 214 | info, err := os.Stat(wdPath) 215 | if err == nil && !info.IsDir() { 216 | return getConfigFromFile(wdPath) 217 | } 218 | 219 | configPath := filepath.Join(configDir, "config.yaml") 220 | info, err = os.Stat(configPath) 221 | if os.IsNotExist(err) { 222 | err = os.MkdirAll(configDir, 0755) 223 | if err != nil { 224 | return nil, err 225 | } 226 | err = os.WriteFile(configPath, []byte("\n"), 0755) 227 | if err != nil { 228 | return nil, err 229 | } 230 | } else if err != nil { 231 | return nil, err 232 | } 233 | 234 | return getConfigFromFile(configPath) 235 | } 236 | 237 | func getWorkspaceContextPath() string { 238 | wd, err := os.Getwd() 239 | if err != nil { 240 | panic(err) 241 | } 242 | 243 | wsDir := getWorkspaceDir(wd) 244 | contextFile := filepath.Join(wsDir, "context.txt") 245 | 246 | return contextFile 247 | } 248 | 249 | func getWorkspaceModelsPath() string { 250 | wd, err := os.Getwd() 251 | if err != nil { 252 | panic(err) 253 | } 254 | 255 | wsDir := getWorkspaceDir(wd) 256 | modelsConfig := filepath.Join(wsDir, "models.yaml") 257 | 258 | return modelsConfig 259 | } 260 | 261 | func getConfigForWd() (*SagePathConfig, error) { 262 | configs, err := getConfigFile() 263 | if err != nil { 264 | return nil, err 265 | } 266 | 267 | wd, err := os.Getwd() 268 | if err != nil { 269 | return nil, err 270 | } 271 | 272 | // TODO: we need to support subdirs by mapping back up to the parent wd 273 | 274 | config := configs.GetConfigForDir(wd) 275 | if config == nil { 276 | config = NewPathConfig(filepath.Base(wd)) 277 | err = config.InitDefaults() 278 | if err != nil { 279 | return nil, err 280 | } 281 | } 282 | 283 | if err != nil { 284 | return nil, err 285 | } 286 | 287 | return config, nil 288 | } 289 | 290 | func getWorkspaceSocketPath(wd string) string { 291 | wsDir := getWorkspaceDir(wd) 292 | 293 | return filepath.Join(wsDir, "language_server.sock") 294 | } 295 | -------------------------------------------------------------------------------- /locality/locality.go: -------------------------------------------------------------------------------- 1 | package locality 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/coder/websocket" 13 | "github.com/google/uuid" 14 | "github.com/rs/zerolog/log" 15 | "go.lsp.dev/protocol" 16 | "go.lsp.dev/uri" 17 | 18 | sitter "github.com/smacker/go-tree-sitter" 19 | "github.com/smacker/go-tree-sitter/golang" 20 | "github.com/smacker/go-tree-sitter/javascript" 21 | "github.com/smacker/go-tree-sitter/python" 22 | "github.com/smacker/go-tree-sitter/typescript/tsx" 23 | "github.com/smacker/go-tree-sitter/typescript/typescript" 24 | ) 25 | 26 | var languageQueries = map[Language]string{ 27 | "go": `(call_expression 28 | function: (selector_expression 29 | field: (field_identifier) @field_function)) 30 | 31 | (call_expression 32 | function: (identifier) @function) 33 | 34 | (composite_literal 35 | type: (qualified_type 36 | package: (package_identifier) 37 | name: (type_identifier) @struct_name)) 38 | 39 | (selector_expression 40 | field: (field_identifier) @field_name)`, 41 | } 42 | 43 | type Language string 44 | 45 | func (l Language) TS() *sitter.Language { 46 | switch l { 47 | case "go": 48 | return golang.GetLanguage() 49 | case "py": 50 | return python.GetLanguage() 51 | case "ts": 52 | return typescript.GetLanguage() 53 | case "tsx": 54 | fallthrough 55 | case "jsx": 56 | return tsx.GetLanguage() 57 | case "js": 58 | return javascript.GetLanguage() 59 | default: 60 | panic("No language set up for " + l) 61 | } 62 | } 63 | 64 | func GetLanguage(fileName string) Language { 65 | return Language(strings.TrimPrefix(filepath.Ext(fileName), ".")) 66 | } 67 | 68 | func GetParser(language *sitter.Language) *sitter.Parser { 69 | parser := sitter.NewParser() 70 | parser.SetLanguage(language) 71 | 72 | return parser 73 | } 74 | 75 | func New(lsp protocol.Server) *Locality { 76 | l := &Locality{ 77 | Lsp: lsp, 78 | listeners: map[string]chan string{}, 79 | } 80 | 81 | go func() { 82 | err := l.Serve() 83 | if err != nil { 84 | panic(err) 85 | } 86 | }() 87 | 88 | return l 89 | } 90 | 91 | type Locality struct { 92 | Lsp protocol.Server 93 | 94 | listeners map[string]chan string 95 | } 96 | 97 | type Context struct { 98 | Captures map[string][]*CodeNode `json:"captures"` 99 | Definitions map[string]string `json:"definitions"` 100 | File string `json:"file"` 101 | Line int `json:"line"` 102 | Queries string `json:"queries"` 103 | } 104 | 105 | func (l *Locality) ServeHTTP(w http.ResponseWriter, r *http.Request) { 106 | c, err := websocket.Accept(w, r, &websocket.AcceptOptions{ 107 | InsecureSkipVerify: true, 108 | }) 109 | if err != nil { 110 | log.Error().Err(err).Msg("Bad connection") 111 | return 112 | } 113 | defer c.CloseNow() 114 | 115 | id := uuid.NewString() 116 | l.listeners[id] = make(chan string) 117 | 118 | defer delete(l.listeners, id) 119 | 120 | // Do we need to check c.Subprotocol? 121 | for newCtxJson := range l.listeners[id] { 122 | // Whenever we get a ping here, check the current state 123 | err = c.Write(context.TODO(), websocket.MessageText, []byte(newCtxJson)) 124 | if err != nil { 125 | log.Error().Err(err).Msg("Bad connection") 126 | return 127 | } 128 | } 129 | 130 | } 131 | 132 | func (l *Locality) Serve() error { 133 | err := http.ListenAndServe(":13579", l) 134 | return err 135 | } 136 | 137 | func (l *Locality) sendUpdate(newCtx string) { 138 | for _, listener := range l.listeners { 139 | listener <- newCtx 140 | } 141 | } 142 | 143 | type Position struct { 144 | Line uint32 `json:"line"` 145 | Column uint32 `json:"column"` 146 | } 147 | 148 | type CodeNode struct { 149 | ID string `json:"id"` 150 | Content string `json:"content"` 151 | Start Position `json:"start"` 152 | End Position `json:"end"` 153 | Type string `json:"type"` 154 | } 155 | 156 | func (l *Locality) GetContext(fileName, content string, line int) (*Context, error) { 157 | language := GetLanguage(fileName) 158 | parser := GetParser(language.TS()) 159 | source := []byte(content) 160 | 161 | tree, err := parser.ParseCtx(context.TODO(), nil, source) 162 | if err != nil { 163 | return nil, err 164 | } 165 | 166 | queries, ok := languageQueries[language] 167 | if !ok { 168 | return nil, fmt.Errorf("No language queries for %s", language) 169 | } 170 | 171 | q, err := sitter.NewQuery([]byte(queries), language.TS()) 172 | if err != nil { 173 | return nil, err 174 | } 175 | 176 | qc := sitter.NewQueryCursor() 177 | qc.Exec(q, tree.RootNode()) 178 | 179 | codeCtx := &Context{ 180 | Captures: map[string][]*CodeNode{}, 181 | Definitions: map[string]string{}, 182 | File: content, 183 | Line: line, 184 | Queries: queries, 185 | } 186 | 187 | for { 188 | m, ok := qc.NextMatch() 189 | if !ok { 190 | break 191 | } 192 | 193 | m = qc.FilterPredicates(m, source) 194 | for _, c := range m.Captures { 195 | // XXX: We also may want to jump to typedef sometimes, not just def 196 | locations, err := l.Lsp.Definition(context.TODO(), &protocol.DefinitionParams{ 197 | TextDocumentPositionParams: protocol.TextDocumentPositionParams{ 198 | TextDocument: protocol.TextDocumentIdentifier{ 199 | URI: uri.File(fileName), 200 | }, 201 | Position: protocol.Position{ 202 | Line: c.Node.StartPoint().Row, 203 | Character: c.Node.StartPoint().Column, 204 | }, 205 | }, 206 | }) 207 | if err != nil { 208 | // XXX: for now, just ignore errors 209 | continue 210 | } 211 | 212 | for _, location := range locations { 213 | uniqueId := fmt.Sprintf("%s %d:%d-%d:%d", location.URI.Filename(), location.Range.Start.Line, location.Range.Start.Character, location.Range.End.Line, location.Range.End.Character) 214 | 215 | codeCtx.Captures[uniqueId] = append(codeCtx.Captures[uniqueId], &CodeNode{ 216 | ID: uniqueId, 217 | Content: c.Node.Content(source), 218 | Start: Position{ 219 | Line: c.Node.StartPoint().Row, 220 | Column: c.Node.StartPoint().Column, 221 | }, 222 | End: Position{ 223 | Line: c.Node.EndPoint().Row, 224 | Column: c.Node.EndPoint().Column, 225 | }, 226 | Type: c.Node.Type(), 227 | }) 228 | 229 | // XXX: HACK: we shouldn't be reading files in this library, we should take some way of being 230 | // provided a file. Can do later. 231 | fileContents, err := os.ReadFile(location.URI.Filename()) 232 | if err != nil { 233 | return nil, err 234 | } 235 | // lsp.GetRangeFromFile(string(fileContents), location.Range) 236 | 237 | definitionFileTree, err := parser.ParseCtx(context.TODO(), nil, fileContents) 238 | if err != nil { 239 | return nil, err 240 | } 241 | 242 | definitionNode := definitionFileTree.RootNode().NamedDescendantForPointRange( 243 | sitter.Point{ 244 | Row: location.Range.Start.Line, 245 | Column: location.Range.Start.Character, 246 | }, 247 | sitter.Point{ 248 | Row: location.Range.End.Line, 249 | Column: location.Range.End.Character, 250 | }, 251 | ) 252 | 253 | for definitionNode.Parent() != nil && definitionNode.Parent().Parent() != nil { 254 | definitionNode = definitionNode.Parent() 255 | } 256 | 257 | codeCtx.Definitions[uniqueId] = definitionNode.Content(fileContents) 258 | 259 | } 260 | } 261 | } 262 | 263 | bs, err := json.Marshal(codeCtx) 264 | if err != nil { 265 | return nil, err 266 | } 267 | 268 | l.sendUpdate(string(bs)) 269 | 270 | return codeCtx, nil 271 | } 272 | -------------------------------------------------------------------------------- /index_cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/md5" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io/fs" 10 | "os" 11 | "path/filepath" 12 | "time" 13 | 14 | "github.com/everestmz/llmcat/treesym" 15 | "github.com/everestmz/llmcat/treesym/language" 16 | "github.com/everestmz/sage/lsp" 17 | "github.com/rs/zerolog/log" 18 | "github.com/spf13/cobra" 19 | "go.lsp.dev/protocol" 20 | "go.lsp.dev/uri" 21 | ) 22 | 23 | func init() { 24 | flags := IndexCmd.PersistentFlags() 25 | 26 | flags.StringP("file", "f", "", "specify a specific file to index for debugging/information purposes") 27 | flags.BoolP("embed", "e", false, "specify whether or not to generate embeddings for symbols") 28 | } 29 | 30 | var IndexCmd = &cobra.Command{ 31 | Use: "index", 32 | Short: "Create or update the sage index for a directory", 33 | RunE: func(cmd *cobra.Command, args []string) error { 34 | wd, err := os.Getwd() 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | config, err := getConfigForWd() 40 | if err != nil { 41 | return err 42 | } 43 | 44 | flags := cmd.Flags() 45 | 46 | shouldEmbed, err := flags.GetBool("embed") 47 | if err != nil { 48 | return err 49 | } 50 | 51 | indexFileOptions := []IndexFileOption{} 52 | if shouldEmbed { 53 | indexFileOptions = append(indexFileOptions, IncludeEmbeddings) 54 | } 55 | 56 | numFiles := 0 57 | 58 | llm, err := NewLLMClient() 59 | if err != nil { 60 | return err 61 | } 62 | 63 | db, err := openDB(wd) 64 | if err != nil { 65 | panic(err) 66 | } 67 | defer db.Close() 68 | 69 | insertCh := make(chan *SymbolInfo, 1000) 70 | defer close(insertCh) 71 | 72 | filePath, err := flags.GetString("file") 73 | if err != nil { 74 | return err 75 | } 76 | 77 | if filePath != "" { 78 | content, err := os.ReadFile(filePath) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | syms, err := indexFile(wd, filePath, content, config, llm, indexFileOptions...) 84 | if err != nil { 85 | return err 86 | } 87 | 88 | bs, _ := json.MarshalIndent(map[string]any{ 89 | "symbols": syms, 90 | }, "", "\t") 91 | fmt.Println(string(bs)) 92 | return nil 93 | } 94 | 95 | numSymbols := 0 96 | resetCounter := 0 97 | 98 | var timedOutFiles []string 99 | 100 | indexFunc := func(path string) error { 101 | log.Debug().Str("path", path).Msg("Inspecting file") 102 | content, err := os.ReadFile(path) 103 | if err != nil { 104 | return err 105 | } 106 | 107 | hash := md5.Sum(content) 108 | hashStr := fmt.Sprintf("%x", hash) 109 | _, ok, err := db.GetFile(path, hashStr) 110 | if err != nil { 111 | return fmt.Errorf("Error getting file: %w", err) 112 | } 113 | if ok { 114 | // We've already indexed this exact file 115 | log.Info().Str("path", path).Str("hash", hashStr).Msg("Skipping indexed file") 116 | return nil 117 | } 118 | 119 | log.Debug().Str("path", path).Str("hash", hashStr).Msg("Indexing file") 120 | tx, err := db.Begin() 121 | if err != nil { 122 | return err 123 | } 124 | defer func() { 125 | err := tx.Commit() 126 | if err != nil { 127 | panic(err) 128 | } 129 | }() 130 | 131 | // Clear out any old info for this file 132 | err = tx.DeleteFileInfoByPath(path) 133 | if err != nil { 134 | return fmt.Errorf("Error deleting file: %w", err) 135 | } 136 | 137 | fileId, err := tx.InsertFile(path, hashStr) 138 | if err != nil { 139 | return fmt.Errorf("Error inserting file: %w", err) 140 | } 141 | 142 | start := time.Now() 143 | 144 | syms, err := indexFile(wd, path, content, config, llm, indexFileOptions...) 145 | if err != nil { 146 | if errors.Is(err, context.DeadlineExceeded) { 147 | timedOutFiles = append(timedOutFiles, path) 148 | } else { 149 | return err 150 | } 151 | } 152 | numSyms := len(syms) 153 | 154 | for _, sym := range syms { 155 | start := sym.Location.Range.Start 156 | end := sym.Location.Range.End 157 | _, err := tx.InsertSymbol(fileId, 158 | float64(sym.Kind), sym.Name, sym.RelativePath, 159 | int(start.Line), int(start.Character), 160 | int(end.Line), int(end.Character), sym.Embedding, 161 | ) 162 | if err != nil { 163 | return err 164 | } 165 | } 166 | 167 | numFiles++ 168 | 169 | numSymbols += numSyms 170 | resetCounter += numSyms 171 | 172 | fmt.Println(fmt.Sprintf("%d (total %d)", numSyms, numSymbols), "symbols in", time.Since(start)) 173 | 174 | return nil 175 | } 176 | 177 | walkFunc := func(path string, d fs.DirEntry, err error) error { 178 | if d.IsDir() { 179 | return nil 180 | } 181 | 182 | if d.Type()&os.ModeSymlink != 0 { 183 | // Need to do since fs.DirEntry doesn't follow symlinks 184 | info, err := os.Stat(path) 185 | if err != nil { 186 | return nil 187 | } 188 | 189 | if info.IsDir() { 190 | return nil 191 | } 192 | } 193 | 194 | shortPath, err := filepath.Rel(wd, path) 195 | if err != nil { 196 | return err 197 | } 198 | 199 | // This will be a no-op if there are no excludes 200 | for i, exc := range config.compiledExcludes { 201 | if exc.Match(shortPath) { 202 | log.Debug().Str("path", shortPath).Str("pattern", config.Exclude[i]).Msg("Skipping file because of match with excludes") 203 | return nil 204 | } 205 | } 206 | 207 | return indexFunc(path) 208 | } 209 | 210 | if len(config.Include) > 0 { 211 | // We should walk the includes if they're directories, and parse them if they're strings 212 | for _, include := range config.Include { 213 | matches, err := filepath.Glob(include) 214 | if err != nil { 215 | return err 216 | } 217 | 218 | for _, match := range matches { 219 | info, err := os.Stat(match) 220 | if err != nil { 221 | return err 222 | } 223 | 224 | absolute, err := filepath.Abs(filepath.Join(wd, match)) 225 | if err != nil { 226 | return err 227 | } 228 | 229 | if info.IsDir() { 230 | err = filepath.WalkDir(absolute, walkFunc) 231 | if err != nil { 232 | return err 233 | } 234 | } else { 235 | err = indexFunc(absolute) 236 | if err != nil { 237 | return err 238 | } 239 | } 240 | } 241 | } 242 | } else { 243 | // We just walk everything, ignoring excludes if they exist 244 | err = filepath.WalkDir(wd, walkFunc) 245 | } 246 | 247 | if len(timedOutFiles) > 0 { 248 | fmt.Println("Timeouts recorded when indexing the following paths:") 249 | for _, path := range timedOutFiles { 250 | fmt.Println("-", path) 251 | } 252 | 253 | } 254 | return err 255 | }, 256 | } 257 | 258 | type IndexFileOption int 259 | 260 | const ( 261 | IncludeEmbeddings IndexFileOption = iota 262 | ) 263 | 264 | func indexFile(wd, path string, content []byte, config *SagePathConfig, llm *LLMClient, options ...IndexFileOption) ([]*SymbolInfo, error) { 265 | var shouldEmbed bool 266 | for _, opt := range options { 267 | switch opt { 268 | case IncludeEmbeddings: 269 | shouldEmbed = true 270 | default: 271 | panic("Unhandled indexing option: " + fmt.Sprint(opt)) 272 | } 273 | } 274 | 275 | relativePath, err := filepath.Rel(wd, path) 276 | if err != nil { 277 | return nil, err 278 | } 279 | 280 | fileUri := uri.File(relativePath) 281 | 282 | processedFile, err := treesym.GetSymbols(context.TODO(), &treesym.SourceFile{ 283 | Path: path, 284 | Text: string(content), 285 | }) 286 | if err == language.ErrUnsupportedExtension { 287 | log.Debug().Msgf("No supported tree-sitter grammar for file %s", path) 288 | return nil, nil 289 | } else if err != nil { 290 | // TODO: some kind of partial functionality? 291 | log.Error().Err(err).Msgf("Unable to extract symbols from %s", path) 292 | return nil, nil 293 | } 294 | 295 | syms := processedFile.Symbols.Definitions 296 | 297 | var result []*SymbolInfo 298 | 299 | for _, sym := range syms { 300 | calculatedInfo := &SymbolInfo{ 301 | SymbolInformation: protocol.SymbolInformation{ 302 | Name: sym.Name, 303 | Kind: lsp.TreeSymKindToLspKind(sym.Kind), 304 | Tags: []protocol.SymbolTag{}, 305 | Deprecated: false, 306 | Location: protocol.Location{ 307 | URI: fileUri, 308 | Range: protocol.Range{ 309 | Start: protocol.Position{ 310 | Line: sym.StartPoint.Row, 311 | Character: sym.StartPoint.Column, 312 | }, 313 | End: protocol.Position{ 314 | Line: sym.EndPoint.Row, 315 | Character: sym.EndPoint.Column, 316 | }, 317 | }, 318 | }, 319 | // Empty container name since we're only getting top-level right now 320 | ContainerName: "", 321 | }, 322 | RelativePath: relativePath, 323 | } 324 | 325 | if shouldEmbed { 326 | symbolText := lsp.GetRangeFromFile(string(content), calculatedInfo.Location.Range) 327 | 328 | prompt := "\n============= INSTRUCTIONS: ================\nDescribe the following code. Be concise, but descriptive. Use domain-specific terms like function names, class names, etc. Make sure you cover as many edge cases and interesting codepaths/features as possible. Above this message is more code from the same file as this symbol. Your response will be indexed for full-text search. DO NOT reproduce the code with comments. Simply write a short but descriptive paragraph about the code:\n" 329 | 330 | prompt += "File: " + path + "\n" 331 | 332 | var explanation string 333 | 334 | // if len(strings.Split(symbolText, "\n")) > 2 { 335 | // explanation, err = llm.GenerateCompletion(context.TODO(), *config.Models.ExplainCode, textDoc.Text+prompt+symbolText) 336 | // if err != nil { 337 | // return nil, err 338 | // } 339 | // } 340 | 341 | prompt = explanation + "\n" + symbolText 342 | 343 | // fmt.Fprintln(os.Stderr, prompt) 344 | 345 | models, err := config.Models.Get() 346 | if err != nil { 347 | return nil, err 348 | } 349 | 350 | embedding, err := llm.GetEmbedding(context.TODO(), *models.Embedding, prompt) 351 | if err != nil { 352 | return nil, err 353 | } 354 | 355 | calculatedInfo.Embedding = embedding 356 | } else { 357 | calculatedInfo.Embedding = nil 358 | } 359 | 360 | result = append(result, calculatedInfo) 361 | } 362 | 363 | return result, nil 364 | } 365 | -------------------------------------------------------------------------------- /db.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "database/sql" 7 | "encoding/binary" 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | 12 | // sqlite_vec "github.com/asg017/sqlite-vec/bindings/go/cgo" 13 | "github.com/mattn/go-sqlite3" 14 | _ "github.com/mattn/go-sqlite3" 15 | "github.com/rs/zerolog/log" 16 | "go.lsp.dev/protocol" 17 | "go.lsp.dev/uri" 18 | ) 19 | 20 | // NOTE; when we enable sqlite-vec again, we'll need to re-enable these LDFLAGS 21 | // To do so, remove the /ignore/ 22 | // /ignore/ #cgo LDFLAGS: -L${SRCDIR}/third_party/lib -Wl,-undefined,dynamic_lookup 23 | // import "C" 24 | 25 | func serializeFloat32(vector []float32) ([]byte, error) { 26 | buf := new(bytes.Buffer) 27 | err := binary.Write(buf, binary.LittleEndian, vector) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return buf.Bytes(), nil 32 | } 33 | 34 | type Execer interface { 35 | Exec(query string, args ...interface{}) (sql.Result, error) 36 | Query(query string, args ...interface{}) (*sql.Rows, error) 37 | QueryRow(query string, args ...interface{}) *sql.Row 38 | } 39 | 40 | type DB struct { 41 | Execer 42 | db *sql.DB 43 | } 44 | 45 | type DBTX struct { 46 | DB 47 | Execer 48 | tx *sql.Tx 49 | } 50 | 51 | func openDB(wd string) (*DB, error) { 52 | // sqlite_vec.Auto() 53 | configDir := getDbsDir() 54 | 55 | dbDir := filepath.Join(configDir, wd) 56 | dbPath := filepath.Join(dbDir, "index.db") 57 | err := os.MkdirAll(dbDir, 0755) 58 | if err != nil { 59 | return nil, fmt.Errorf("Error creating directory %s: %w", dbDir, err) 60 | } 61 | 62 | db, err := sql.Open("sqlite3", dbPath) 63 | if err != nil { 64 | return nil, fmt.Errorf("Error opening db %s: %w", dbPath, err) 65 | } 66 | 67 | var sqliteVersion, vecVersion string 68 | // err = db.QueryRow("select sqlite_version(), vec_version()").Scan(&sqliteVersion, &vecVersion) 69 | vecVersion = "sqlite-vec disabled until needed" 70 | err = db.QueryRow("select sqlite_version()").Scan(&sqliteVersion) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | log.Debug().Str("sqlite_version", sqliteVersion).Str("vec_version", vecVersion).Msg("Initialized DB") 76 | 77 | result := &DB{ 78 | Execer: db, 79 | db: db, 80 | } 81 | 82 | return result, result.Init() 83 | } 84 | 85 | func (db *DB) Close() error { 86 | return db.db.Close() 87 | } 88 | 89 | func (db *DB) Begin() (*DBTX, error) { 90 | tx, err := db.db.BeginTx(context.TODO(), &sql.TxOptions{}) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | return &DBTX{ 96 | DB: DB{ 97 | Execer: tx, 98 | db: db.db, 99 | }, 100 | 101 | Execer: tx, 102 | tx: tx, 103 | }, nil 104 | } 105 | 106 | func (dbtx *DBTX) Rollback() error { 107 | return dbtx.tx.Rollback() 108 | } 109 | 110 | func (dbtx *DBTX) Commit() error { 111 | return dbtx.tx.Commit() 112 | } 113 | 114 | func (db *DB) Init() error { 115 | _, err := db.Exec(`CREATE TABLE IF NOT EXISTS file ( 116 | id INTEGER PRIMARY KEY, 117 | path TEXT NOT NULL, 118 | md5 TEXT NOT NULL 119 | );`) 120 | if err != nil { 121 | return fmt.Errorf("Error creating file table: %w", err) 122 | } 123 | 124 | _, err = db.Exec(`CREATE TABLE IF NOT EXISTS symbol ( 125 | id INTEGER PRIMARY KEY, 126 | kind REAL NOT NULL, 127 | name TEXT NOT NULL, 128 | path TEXT NOT NULL, 129 | start_line INTEGER NOT NULL, 130 | start_col INTEGER NOT NULL, 131 | end_line INTEGER NOT NULL, 132 | end_col INTEGER NOT NULL, 133 | file_id INTEGER NOT NULL, 134 | FOREIGN KEY(file_id) REFERENCES file(id) 135 | );`) 136 | if err != nil { 137 | return fmt.Errorf("Error creating symbol table: %w", err) 138 | } 139 | 140 | // TODO: handle different vector dimension sizes 141 | // XXX: commenting out until we have use for embeddings 142 | // _, err = db.Exec(`CREATE VIRTUAL TABLE IF NOT EXISTS symbol_embedding USING vec0(embedding float[768]);`) 143 | // if err != nil { 144 | // return fmt.Errorf("Error creating symbol embeddings table: %w", err) 145 | // } 146 | 147 | // _, err = db.Exec("CREATE VIRTUAL TABLE IF NOT EXISTS symbol_fts USING fts5(name, content=symbol, content_rowid=id);") 148 | // if err != nil { 149 | // return fmt.Errorf("Error creating symbol fts table: %w", err) 150 | // } 151 | 152 | // _, err = db.Exec(` 153 | // CREATE TRIGGER IF NOT EXISTS symbol_ai AFTER INSERT ON symbol BEGIN 154 | // INSERT INTO symbol_fts(rowid, name) VALUES (new.id, new.name); 155 | // END; 156 | // CREATE TRIGGER IF NOT EXISTS symbol_ad AFTER DELETE ON symbol BEGIN 157 | // INSERT INTO symbol_fts(fts_idx, rowid, name) VALUES('delete', old.id, old.name); 158 | // END; 159 | // CREATE TRIGGER IF NOT EXISTS symbol_au AFTER UPDATE ON symbol BEGIN 160 | // INSERT INTO symbol_fts(fts_idx, rowid, name) VALUES('delete', old.id, old.name); 161 | // INSERT INTO symbol_fts(rowid, name) VALUES (new.id, new.name); 162 | // END; 163 | // `) 164 | // if err != nil { 165 | // return fmt.Errorf("Error creating symbol : %w", err) 166 | // } 167 | 168 | return nil 169 | } 170 | 171 | func (db *DB) InsertFile(path, md5 string) (int64, error) { 172 | result, err := db.Exec("INSERT INTO file (path, md5) VALUES (?, ?) RETURNING id;", path, md5) 173 | if err != nil { 174 | return 0, err 175 | } 176 | 177 | return result.LastInsertId() 178 | } 179 | 180 | func (db *DB) DeleteFileInfo(id int64) error { 181 | _, err := db.Exec("DELETE FROM symbol WHERE file_id = ?;", id) 182 | if err != nil { 183 | return err 184 | } 185 | 186 | _, err = db.Exec("DELETE FROM file WHERE id = ?;", id) 187 | return err 188 | } 189 | 190 | func (db *DB) DeleteFileInfoByPath(path string) error { 191 | idRows, err := db.Query("SELECT id FROM file WHERE path = ?;", path) 192 | if err != nil && err != sql.ErrNoRows { 193 | return err 194 | } 195 | 196 | for idRows.Next() { 197 | var fileId int64 198 | err = idRows.Scan(&fileId) 199 | if err != nil { 200 | return err 201 | } 202 | 203 | _, err = db.Exec("DELETE FROM symbol WHERE file_id = ?", fileId) 204 | if err != nil { 205 | return err 206 | } 207 | } 208 | if err := idRows.Err(); err != nil { 209 | return err 210 | } 211 | 212 | _, err = db.Exec("DELETE FROM file WHERE path = ?;", path) 213 | return err 214 | } 215 | 216 | func (db *DB) GetFile(path, md5 string) (int64, bool, error) { 217 | result, err := db.Query("SELECT id FROM file WHERE path = ? AND md5 = ?;", path, md5) 218 | if err != nil { 219 | return 0, false, err 220 | } 221 | defer result.Close() 222 | 223 | var id int64 224 | // We know there's at least one row 225 | ok := result.Next() 226 | if !ok { 227 | return 0, false, nil 228 | } 229 | err = result.Scan(&id) 230 | if err != nil { 231 | return 0, false, err 232 | } 233 | 234 | return id, true, nil 235 | } 236 | 237 | func (db *DB) float64ArrayToFloat32(embedding []float64) []float32 { 238 | // XXX: this might be super bad. There's an open issue to support f64 which we should track 239 | // 240 | var embedding32 []float32 = make([]float32, len(embedding)) 241 | for i, v := range embedding { 242 | embedding32[i] = float32(v) 243 | } 244 | 245 | return embedding32 246 | } 247 | 248 | func (db *DB) InsertSymbol(fileId int64, kind float64, name, path string, startL, startC, endL, endC int, embedding []float64) (int64, error) { 249 | serialized, err := serializeFloat32(db.float64ArrayToFloat32(embedding)) 250 | if err != nil { 251 | return 0, err 252 | } 253 | 254 | result, err := db.Exec( 255 | "INSERT INTO symbol (file_id, kind, name, path, start_line, start_col, end_line, end_col) VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING id;", 256 | fileId, kind, name, path, startL, startC, endL, endC, 257 | ) 258 | if err != nil { 259 | return 0, err 260 | } 261 | 262 | symbolId, err := result.LastInsertId() 263 | if err != nil { 264 | return 0, err 265 | } 266 | 267 | if embedding != nil { 268 | _, err = db.Exec("INSERT INTO symbol_embedding (rowid, embedding) VALUES (?, ?);", symbolId, serialized) 269 | if err != nil { 270 | return 0, err 271 | } 272 | } 273 | 274 | return symbolId, nil 275 | } 276 | 277 | func (db *DB) FindSymbolByEmbedding(embedding []float64) ([]protocol.SymbolInformation, error) { 278 | serialized, err := serializeFloat32(db.float64ArrayToFloat32(embedding)) 279 | if err != nil { 280 | return nil, err 281 | } 282 | 283 | rows, err := db.Query(`SELECT kind, name, path, start_line, start_col, end_line, end_col FROM symbol WHERE id IN (SELECT rowid FROM symbol_embedding WHERE embedding MATCH ? ORDER BY distance LIMIT 10);`, serialized) 284 | if err != nil { 285 | sqliteErr, ok := err.(sqlite3.Error) 286 | if ok { 287 | fmt.Println(sqliteErr.Code, sqliteErr.ExtendedCode, sqliteErr.ExtendedCode.Error()) 288 | } 289 | return nil, err 290 | } 291 | 292 | var syms []protocol.SymbolInformation 293 | 294 | for rows.Next() { 295 | info, err := db.scanSymbolRow(rows) 296 | if err != nil { 297 | return nil, err 298 | } 299 | 300 | syms = append(syms, *info) 301 | } 302 | 303 | return syms, nil 304 | } 305 | 306 | func (db *DB) scanSymbolRow(rows *sql.Rows) (*protocol.SymbolInformation, error) { 307 | var name, path string 308 | var kind float64 309 | var startLine, startCol, endLine, endCol uint32 310 | err := rows.Scan(&kind, &name, &path, &startLine, &startCol, &endLine, &endCol) 311 | if err != nil { 312 | return nil, err 313 | } 314 | 315 | return &protocol.SymbolInformation{ 316 | Name: name, 317 | Kind: protocol.SymbolKind(kind), 318 | Location: protocol.Location{ 319 | URI: uri.File(path), 320 | Range: protocol.Range{ 321 | Start: protocol.Position{ 322 | Line: startLine, 323 | Character: startCol, 324 | }, 325 | End: protocol.Position{ 326 | Line: endLine, 327 | Character: endCol, 328 | }, 329 | }, 330 | }, 331 | }, nil 332 | } 333 | 334 | func (db *DB) FindSymbolByPrefix(prefix string) ([]protocol.SymbolInformation, error) { 335 | rows, err := db.Query(`SELECT kind, name, path, start_line, start_col, end_line, end_col FROM symbol WHERE name LIKE ? LIMIT 100`, fmt.Sprintf("%s%%", prefix)) 336 | if err != nil { 337 | return nil, err 338 | } 339 | 340 | result := []protocol.SymbolInformation{} 341 | 342 | for rows.Next() { 343 | info, err := db.scanSymbolRow(rows) 344 | if err != nil { 345 | return nil, err 346 | } 347 | 348 | result = append(result, *info) 349 | } 350 | 351 | return result, nil 352 | } 353 | -------------------------------------------------------------------------------- /rpc/languageserver/LanguageServerState.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.35.2 4 | // protoc v5.28.2 5 | // source: rpc/languageserver/LanguageServerState.proto 6 | 7 | package languageserver 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | timestamppb "google.golang.org/protobuf/types/known/timestamppb" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | type TextDocument struct { 25 | state protoimpl.MessageState 26 | sizeCache protoimpl.SizeCache 27 | unknownFields protoimpl.UnknownFields 28 | 29 | Uri string `protobuf:"bytes,1,opt,name=uri,proto3" json:"uri,omitempty"` 30 | LanguageId string `protobuf:"bytes,2,opt,name=language_id,json=languageId,proto3" json:"language_id,omitempty"` 31 | Version int32 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"` 32 | Text string `protobuf:"bytes,4,opt,name=text,proto3" json:"text,omitempty"` 33 | LastEdit *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=last_edit,json=lastEdit,proto3" json:"last_edit,omitempty"` 34 | LastEditedLine int32 `protobuf:"varint,6,opt,name=last_edited_line,json=lastEditedLine,proto3" json:"last_edited_line,omitempty"` 35 | } 36 | 37 | func (x *TextDocument) Reset() { 38 | *x = TextDocument{} 39 | mi := &file_rpc_languageserver_LanguageServerState_proto_msgTypes[0] 40 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 41 | ms.StoreMessageInfo(mi) 42 | } 43 | 44 | func (x *TextDocument) String() string { 45 | return protoimpl.X.MessageStringOf(x) 46 | } 47 | 48 | func (*TextDocument) ProtoMessage() {} 49 | 50 | func (x *TextDocument) ProtoReflect() protoreflect.Message { 51 | mi := &file_rpc_languageserver_LanguageServerState_proto_msgTypes[0] 52 | if x != nil { 53 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 54 | if ms.LoadMessageInfo() == nil { 55 | ms.StoreMessageInfo(mi) 56 | } 57 | return ms 58 | } 59 | return mi.MessageOf(x) 60 | } 61 | 62 | // Deprecated: Use TextDocument.ProtoReflect.Descriptor instead. 63 | func (*TextDocument) Descriptor() ([]byte, []int) { 64 | return file_rpc_languageserver_LanguageServerState_proto_rawDescGZIP(), []int{0} 65 | } 66 | 67 | func (x *TextDocument) GetUri() string { 68 | if x != nil { 69 | return x.Uri 70 | } 71 | return "" 72 | } 73 | 74 | func (x *TextDocument) GetLanguageId() string { 75 | if x != nil { 76 | return x.LanguageId 77 | } 78 | return "" 79 | } 80 | 81 | func (x *TextDocument) GetVersion() int32 { 82 | if x != nil { 83 | return x.Version 84 | } 85 | return 0 86 | } 87 | 88 | func (x *TextDocument) GetText() string { 89 | if x != nil { 90 | return x.Text 91 | } 92 | return "" 93 | } 94 | 95 | func (x *TextDocument) GetLastEdit() *timestamppb.Timestamp { 96 | if x != nil { 97 | return x.LastEdit 98 | } 99 | return nil 100 | } 101 | 102 | func (x *TextDocument) GetLastEditedLine() int32 { 103 | if x != nil { 104 | return x.LastEditedLine 105 | } 106 | return 0 107 | } 108 | 109 | type GetOpenDocumentsRequest struct { 110 | state protoimpl.MessageState 111 | sizeCache protoimpl.SizeCache 112 | unknownFields protoimpl.UnknownFields 113 | } 114 | 115 | func (x *GetOpenDocumentsRequest) Reset() { 116 | *x = GetOpenDocumentsRequest{} 117 | mi := &file_rpc_languageserver_LanguageServerState_proto_msgTypes[1] 118 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 119 | ms.StoreMessageInfo(mi) 120 | } 121 | 122 | func (x *GetOpenDocumentsRequest) String() string { 123 | return protoimpl.X.MessageStringOf(x) 124 | } 125 | 126 | func (*GetOpenDocumentsRequest) ProtoMessage() {} 127 | 128 | func (x *GetOpenDocumentsRequest) ProtoReflect() protoreflect.Message { 129 | mi := &file_rpc_languageserver_LanguageServerState_proto_msgTypes[1] 130 | if x != nil { 131 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 132 | if ms.LoadMessageInfo() == nil { 133 | ms.StoreMessageInfo(mi) 134 | } 135 | return ms 136 | } 137 | return mi.MessageOf(x) 138 | } 139 | 140 | // Deprecated: Use GetOpenDocumentsRequest.ProtoReflect.Descriptor instead. 141 | func (*GetOpenDocumentsRequest) Descriptor() ([]byte, []int) { 142 | return file_rpc_languageserver_LanguageServerState_proto_rawDescGZIP(), []int{1} 143 | } 144 | 145 | type GetOpenDocumentsResponse struct { 146 | state protoimpl.MessageState 147 | sizeCache protoimpl.SizeCache 148 | unknownFields protoimpl.UnknownFields 149 | 150 | Documents map[string]*TextDocument `protobuf:"bytes,1,rep,name=documents,proto3" json:"documents,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` 151 | } 152 | 153 | func (x *GetOpenDocumentsResponse) Reset() { 154 | *x = GetOpenDocumentsResponse{} 155 | mi := &file_rpc_languageserver_LanguageServerState_proto_msgTypes[2] 156 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 157 | ms.StoreMessageInfo(mi) 158 | } 159 | 160 | func (x *GetOpenDocumentsResponse) String() string { 161 | return protoimpl.X.MessageStringOf(x) 162 | } 163 | 164 | func (*GetOpenDocumentsResponse) ProtoMessage() {} 165 | 166 | func (x *GetOpenDocumentsResponse) ProtoReflect() protoreflect.Message { 167 | mi := &file_rpc_languageserver_LanguageServerState_proto_msgTypes[2] 168 | if x != nil { 169 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 170 | if ms.LoadMessageInfo() == nil { 171 | ms.StoreMessageInfo(mi) 172 | } 173 | return ms 174 | } 175 | return mi.MessageOf(x) 176 | } 177 | 178 | // Deprecated: Use GetOpenDocumentsResponse.ProtoReflect.Descriptor instead. 179 | func (*GetOpenDocumentsResponse) Descriptor() ([]byte, []int) { 180 | return file_rpc_languageserver_LanguageServerState_proto_rawDescGZIP(), []int{2} 181 | } 182 | 183 | func (x *GetOpenDocumentsResponse) GetDocuments() map[string]*TextDocument { 184 | if x != nil { 185 | return x.Documents 186 | } 187 | return nil 188 | } 189 | 190 | var File_rpc_languageserver_LanguageServerState_proto protoreflect.FileDescriptor 191 | 192 | var file_rpc_languageserver_LanguageServerState_proto_rawDesc = []byte{ 193 | 0x0a, 0x2c, 0x72, 0x70, 0x63, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x73, 0x65, 194 | 0x72, 0x76, 0x65, 0x72, 0x2f, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x53, 0x65, 0x72, 195 | 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 196 | 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x1a, 0x1f, 197 | 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 198 | 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 199 | 0xd2, 0x01, 0x0a, 0x0c, 0x54, 0x65, 0x78, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 200 | 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 201 | 0x72, 0x69, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x5f, 0x69, 202 | 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 203 | 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 204 | 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 205 | 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 206 | 0x74, 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x64, 0x69, 0x74, 0x18, 0x05, 207 | 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 208 | 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 209 | 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x45, 0x64, 0x69, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x61, 210 | 0x73, 0x74, 0x5f, 0x65, 0x64, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x06, 211 | 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x45, 0x64, 0x69, 0x74, 0x65, 0x64, 212 | 0x4c, 0x69, 0x6e, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x44, 213 | 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 214 | 0xcd, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 215 | 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x09, 216 | 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 217 | 0x37, 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 218 | 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 219 | 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 220 | 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 221 | 0x6e, 0x74, 0x73, 0x1a, 0x5a, 0x0a, 0x0e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 222 | 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 223 | 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 224 | 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 225 | 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x78, 0x74, 0x44, 0x6f, 0x63, 0x75, 226 | 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 227 | 0x7e, 0x0a, 0x13, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 228 | 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x67, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 229 | 0x6e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x6c, 0x61, 0x6e, 230 | 0x67, 0x75, 0x61, 0x67, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x4f, 231 | 0x70, 0x65, 0x6e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 232 | 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x73, 0x65, 233 | 0x72, 0x76, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x44, 0x6f, 0x63, 0x75, 234 | 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 235 | 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x76, 236 | 0x65, 0x72, 0x65, 0x73, 0x74, 0x6d, 0x7a, 0x2f, 0x73, 0x61, 0x67, 0x65, 0x2f, 0x72, 0x70, 0x63, 237 | 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x62, 238 | 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 239 | } 240 | 241 | var ( 242 | file_rpc_languageserver_LanguageServerState_proto_rawDescOnce sync.Once 243 | file_rpc_languageserver_LanguageServerState_proto_rawDescData = file_rpc_languageserver_LanguageServerState_proto_rawDesc 244 | ) 245 | 246 | func file_rpc_languageserver_LanguageServerState_proto_rawDescGZIP() []byte { 247 | file_rpc_languageserver_LanguageServerState_proto_rawDescOnce.Do(func() { 248 | file_rpc_languageserver_LanguageServerState_proto_rawDescData = protoimpl.X.CompressGZIP(file_rpc_languageserver_LanguageServerState_proto_rawDescData) 249 | }) 250 | return file_rpc_languageserver_LanguageServerState_proto_rawDescData 251 | } 252 | 253 | var file_rpc_languageserver_LanguageServerState_proto_msgTypes = make([]protoimpl.MessageInfo, 4) 254 | var file_rpc_languageserver_LanguageServerState_proto_goTypes = []any{ 255 | (*TextDocument)(nil), // 0: languageserver.TextDocument 256 | (*GetOpenDocumentsRequest)(nil), // 1: languageserver.GetOpenDocumentsRequest 257 | (*GetOpenDocumentsResponse)(nil), // 2: languageserver.GetOpenDocumentsResponse 258 | nil, // 3: languageserver.GetOpenDocumentsResponse.DocumentsEntry 259 | (*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp 260 | } 261 | var file_rpc_languageserver_LanguageServerState_proto_depIdxs = []int32{ 262 | 4, // 0: languageserver.TextDocument.last_edit:type_name -> google.protobuf.Timestamp 263 | 3, // 1: languageserver.GetOpenDocumentsResponse.documents:type_name -> languageserver.GetOpenDocumentsResponse.DocumentsEntry 264 | 0, // 2: languageserver.GetOpenDocumentsResponse.DocumentsEntry.value:type_name -> languageserver.TextDocument 265 | 1, // 3: languageserver.LanguageServerState.GetOpenDocuments:input_type -> languageserver.GetOpenDocumentsRequest 266 | 2, // 4: languageserver.LanguageServerState.GetOpenDocuments:output_type -> languageserver.GetOpenDocumentsResponse 267 | 4, // [4:5] is the sub-list for method output_type 268 | 3, // [3:4] is the sub-list for method input_type 269 | 3, // [3:3] is the sub-list for extension type_name 270 | 3, // [3:3] is the sub-list for extension extendee 271 | 0, // [0:3] is the sub-list for field type_name 272 | } 273 | 274 | func init() { file_rpc_languageserver_LanguageServerState_proto_init() } 275 | func file_rpc_languageserver_LanguageServerState_proto_init() { 276 | if File_rpc_languageserver_LanguageServerState_proto != nil { 277 | return 278 | } 279 | type x struct{} 280 | out := protoimpl.TypeBuilder{ 281 | File: protoimpl.DescBuilder{ 282 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 283 | RawDescriptor: file_rpc_languageserver_LanguageServerState_proto_rawDesc, 284 | NumEnums: 0, 285 | NumMessages: 4, 286 | NumExtensions: 0, 287 | NumServices: 1, 288 | }, 289 | GoTypes: file_rpc_languageserver_LanguageServerState_proto_goTypes, 290 | DependencyIndexes: file_rpc_languageserver_LanguageServerState_proto_depIdxs, 291 | MessageInfos: file_rpc_languageserver_LanguageServerState_proto_msgTypes, 292 | }.Build() 293 | File_rpc_languageserver_LanguageServerState_proto = out.File 294 | file_rpc_languageserver_LanguageServerState_proto_rawDesc = nil 295 | file_rpc_languageserver_LanguageServerState_proto_goTypes = nil 296 | file_rpc_languageserver_LanguageServerState_proto_depIdxs = nil 297 | } 298 | -------------------------------------------------------------------------------- /code_actions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | "strings" 9 | 10 | cursor "github.com/everestmz/cursor-rpc" 11 | aiserverv1 "github.com/everestmz/cursor-rpc/cursor/gen/aiserver/v1" 12 | 13 | "github.com/rs/zerolog" 14 | "go.lsp.dev/protocol" 15 | "go.lsp.dev/uri" 16 | ) 17 | 18 | var lspCommands = []*CommandDefinition{ 19 | lspCommandExecCursorCompletion, 20 | lspCommandExecCompletion, 21 | lspCommandOpenModelsConfig, 22 | lspCommandOpenContextConfig, 23 | lspCommandShowCurrentContext, 24 | lspCommandShowCurrentModel, 25 | } 26 | 27 | func getPositionOffset(text string) protocol.Position { 28 | newLines := strings.Count(text, "\n") 29 | var cols = len(text) 30 | if newLines > 0 { 31 | spl := strings.Split(text, "\n") 32 | cols = len(spl[newLines]) 33 | } 34 | 35 | return protocol.Position{ 36 | Line: uint32(newLines), 37 | Character: uint32(cols), 38 | } 39 | } 40 | 41 | var lspCommandShowCurrentModel = &CommandDefinition{ 42 | Title: "Show current Ollama model", 43 | ShowCodeAction: false, 44 | Identifier: "sage.workspace.configuration.model", 45 | BuildArgs: func(params *protocol.CodeActionParams) ([]any, error) { 46 | return []any{}, nil 47 | }, 48 | Execute: func(params *protocol.ExecuteCommandParams, client LspClient, clientInfo *LanguageServerClientInfo) (*protocol.ApplyWorkspaceEditParams, error) { 49 | client.Progress(context.TODO(), &protocol.ProgressParams{ 50 | // Token: *params.WorkDoneProgressParams.WorkDoneToken, 51 | Value: &protocol.WorkDoneProgressBegin{ 52 | Kind: protocol.WorkDoneProgressKindBegin, 53 | Title: "Ollama model", 54 | Message: "getting model...", 55 | }, 56 | }) 57 | 58 | models, err := clientInfo.Config.Models.Get() 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | client.Progress(context.TODO(), &protocol.ProgressParams{ 64 | // Token: *params.WorkDoneProgressParams.WorkDoneToken, 65 | Value: &protocol.WorkDoneProgressEnd{ 66 | Kind: protocol.WorkDoneProgressKindEnd, 67 | Message: *models.Default, 68 | }, 69 | }) 70 | 71 | return nil, nil 72 | }, 73 | } 74 | 75 | var lspCommandShowCurrentContext = &CommandDefinition{ 76 | Title: "Show context", 77 | ShowCodeAction: false, 78 | Identifier: "sage.workspace.context.show", 79 | BuildArgs: func(params *protocol.CodeActionParams) ([]any, error) { 80 | return []any{}, nil 81 | }, 82 | Execute: func(params *protocol.ExecuteCommandParams, client LspClient, clientInfo *LanguageServerClientInfo) (*protocol.ApplyWorkspaceEditParams, error) { 83 | providers, err := clientInfo.Config.Context.Get() 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | llmContext, err := BuildContext(providers, clientInfo) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | f, err := os.CreateTemp(os.TempDir(), "sage_context") 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | _, err = f.WriteString(llmContext) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | filename := f.Name() 104 | 105 | f.Close() 106 | 107 | result := &protocol.ShowDocumentResult{} 108 | _, err = client.Conn().Call(context.TODO(), string(protocol.MethodShowDocument), protocol.ShowDocumentParams{ 109 | URI: uri.File(filename), 110 | External: false, 111 | TakeFocus: true, 112 | Selection: nil, 113 | }, result) 114 | 115 | return nil, err 116 | }, 117 | } 118 | 119 | var lspCommandOpenContextConfig = &CommandDefinition{ 120 | Title: "Edit context", 121 | ShowCodeAction: true, 122 | Identifier: "sage.workspace.context.edit", 123 | BuildArgs: func(params *protocol.CodeActionParams) ([]any, error) { 124 | return []any{}, nil 125 | }, 126 | Execute: func(params *protocol.ExecuteCommandParams, client LspClient, clientInfo *LanguageServerClientInfo) (*protocol.ApplyWorkspaceEditParams, error) { 127 | result := &protocol.ShowDocumentResult{} 128 | _, err := client.Conn().Call(context.TODO(), string(protocol.MethodShowDocument), protocol.ShowDocumentParams{ 129 | URI: uri.File(getWorkspaceContextPath()), 130 | External: false, 131 | TakeFocus: true, 132 | Selection: nil, 133 | }, result) 134 | 135 | return nil, err 136 | }, 137 | } 138 | 139 | var lspCommandOpenModelsConfig = &CommandDefinition{ 140 | Title: "Edit workspace configuration", 141 | ShowCodeAction: true, 142 | Identifier: "sage.workspace.configuration.edit", 143 | BuildArgs: func(params *protocol.CodeActionParams) ([]any, error) { 144 | return []any{}, nil 145 | }, 146 | Execute: func(params *protocol.ExecuteCommandParams, client LspClient, clientInfo *LanguageServerClientInfo) (*protocol.ApplyWorkspaceEditParams, error) { 147 | result := &protocol.ShowDocumentResult{} 148 | _, err := client.Conn().Call(context.TODO(), string(protocol.MethodShowDocument), protocol.ShowDocumentParams{ 149 | URI: uri.File(getWorkspaceModelsPath()), 150 | External: false, 151 | TakeFocus: true, 152 | Selection: nil, 153 | }, result) 154 | 155 | return nil, err 156 | }, 157 | } 158 | 159 | var lspCommandExecCursorCompletion = &CommandDefinition{ 160 | Title: "Cursor generate", 161 | ShowCodeAction: true, 162 | Identifier: "sage.completion.cursor.selection", 163 | BuildArgs: func(params *protocol.CodeActionParams) ([]any, error) { 164 | args := &LlmCompletionArgs{ 165 | Filename: params.TextDocument.URI, 166 | Selection: params.Range, 167 | } 168 | 169 | return []any{args}, nil 170 | }, 171 | Execute: func(params *protocol.ExecuteCommandParams, client LspClient, clientInfo *LanguageServerClientInfo) (*protocol.ApplyWorkspaceEditParams, error) { 172 | lsLogger := globalLsLogger.With().Str("code_action", "sage.completion.selection").Logger() 173 | argBs, err := json.Marshal(params.Arguments[0]) 174 | if err != nil { 175 | return nil, err 176 | } 177 | 178 | // TODO: move this out into some global initializer 179 | cursorCredentials, err := cursor.GetDefaultCredentials() 180 | if err != nil { 181 | return nil, err 182 | } 183 | 184 | args := &LlmCompletionArgs{} 185 | err = json.Unmarshal(argBs, args) 186 | if err != nil { 187 | return nil, err 188 | } 189 | 190 | prompt, err := buildPrompt(lsLogger, args, clientInfo) 191 | if err != nil { 192 | return nil, err 193 | } 194 | 195 | editsManager := NewLlmResponseEditsManager(client, args.Filename, args.Selection.End.Line, args.Selection.End.Character) 196 | 197 | aiClient := cursor.NewAiServiceClient() 198 | 199 | model := "claude-3.5-sonnet" 200 | 201 | resp, err := aiClient.StreamChat(context.TODO(), cursor.NewRequest(cursorCredentials, &aiserverv1.GetChatRequest{ 202 | ModelDetails: &aiserverv1.ModelDetails{ 203 | ModelName: &model, 204 | }, 205 | Conversation: []*aiserverv1.ConversationMessage{ 206 | { 207 | Text: prompt, 208 | Type: aiserverv1.ConversationMessage_MESSAGE_TYPE_HUMAN, 209 | }, 210 | }, 211 | })) 212 | 213 | if err != nil { 214 | return nil, err 215 | } 216 | 217 | for resp.Receive() { 218 | next := resp.Msg() 219 | editsManager.NextEdit(next.Text) 220 | } 221 | 222 | return nil, nil 223 | }, 224 | } 225 | 226 | func buildPrompt(lsLogger zerolog.Logger, args *LlmCompletionArgs, clientInfo *LanguageServerClientInfo) (string, error) { 227 | textDocument, ok := clientInfo.Docs.GetOpenDocument(args.Filename) 228 | if !ok { 229 | return "", fmt.Errorf("No text document for supposedly open file %s", args.Filename) 230 | } 231 | 232 | documentLines := append(strings.Split(textDocument.Text, "\n"), "") // Unixy files end in \n 233 | lineRange := documentLines[args.Selection.Start.Line : args.Selection.End.Line+1] 234 | lsLogger.Debug().Str("Line range", fmt.Sprint(lineRange)).Msg("Lines Range") 235 | endLineIndex := args.Selection.End.Line - args.Selection.Start.Line 236 | lsLogger.Debug().Uint32("end idx", endLineIndex).Msg("End idx") 237 | lineRange[0] = lineRange[0][args.Selection.Start.Character:] 238 | lineRange[endLineIndex] = lineRange[endLineIndex][:args.Selection.End.Character] 239 | lsLogger.Debug().Str("Line range", fmt.Sprint(lineRange)).Msg("Lines Range after narrowing") 240 | 241 | documentContext := strings.Join(documentLines[:args.Selection.End.Line], "\n") 242 | selectionText := strings.Join(lineRange, "\n") 243 | 244 | contextProviders, err := clientInfo.Config.Context.Get() 245 | if err != nil { 246 | return "", err 247 | } 248 | 249 | filesContext, err := BuildContext(contextProviders, clientInfo) 250 | if err != nil { 251 | return "", err 252 | } 253 | 254 | prompt := filesContext 255 | 256 | prompt += "\n" 257 | prompt += documentContext 258 | prompt += "\n\n" 259 | 260 | prompt += ` 261 | A user's prompt, in the form of a question, or a description code to write, is shown below. Satisfy the user's prompt or question to the best of your ability. If asked to complete code, DO NOT type out any extra text, or backticks since your response will be appended to the end of the CurrentFile. DO NOT regurgitate the whole file. Simply return the new code, or the modified code. 262 | 263 | ` 264 | 265 | prompt += "\n" 266 | prompt += selectionText 267 | prompt += "\n\n" 268 | 269 | return prompt, nil 270 | } 271 | 272 | type LlmResponseEditsManager struct { 273 | placeNextEdit protocol.Position 274 | fullText string 275 | currentLine string 276 | client LspClient 277 | filename uri.URI 278 | } 279 | 280 | func (m *LlmResponseEditsManager) NextEdit(nextText string) { 281 | m.fullText += nextText 282 | 283 | if strings.Contains(nextText, "\n") { 284 | spl := strings.Split(nextText, "\n") 285 | m.currentLine += spl[0] 286 | 287 | m.client.Progress(context.TODO(), &protocol.ProgressParams{ 288 | // Token: *params.WorkDoneProgressParams.WorkDoneToken, 289 | Value: &protocol.WorkDoneProgressReport{ 290 | Kind: protocol.WorkDoneProgressKindReport, 291 | Message: "sage: " + m.currentLine, 292 | }, 293 | }) 294 | m.currentLine = strings.Join(spl[1:], "\n") 295 | } else { 296 | m.currentLine += nextText 297 | } 298 | 299 | m.client.ApplyEdit(context.TODO(), &protocol.ApplyWorkspaceEditParams{ 300 | Label: "llm_line", 301 | Edit: protocol.WorkspaceEdit{ 302 | Changes: map[uri.URI][]protocol.TextEdit{ 303 | m.filename: []protocol.TextEdit{ 304 | { 305 | Range: protocol.Range{ 306 | Start: m.placeNextEdit, 307 | End: m.placeNextEdit, 308 | }, 309 | NewText: nextText, 310 | }, 311 | }, 312 | }, 313 | }, 314 | }) 315 | offset := getPositionOffset(nextText) 316 | m.placeNextEdit.Line += offset.Line 317 | m.placeNextEdit.Character += offset.Character 318 | if offset.Line > 0 { 319 | m.placeNextEdit.Character = offset.Character 320 | } 321 | 322 | } 323 | 324 | func NewLlmResponseEditsManager(client LspClient, filename uri.URI, startLine, startChar uint32) *LlmResponseEditsManager { 325 | return &LlmResponseEditsManager{ 326 | placeNextEdit: protocol.Position{ 327 | Line: startLine, 328 | Character: startChar, 329 | }, 330 | filename: filename, 331 | client: client, 332 | } 333 | } 334 | 335 | var lspCommandExecCompletion = &CommandDefinition{ 336 | Title: "Ollama generate", 337 | ShowCodeAction: true, 338 | Identifier: "sage.completion.ollama.selection", 339 | BuildArgs: func(params *protocol.CodeActionParams) ([]any, error) { 340 | args := &LlmCompletionArgs{ 341 | Filename: params.TextDocument.URI, 342 | Selection: params.Range, 343 | } 344 | 345 | return []any{args}, nil 346 | }, 347 | Execute: func(params *protocol.ExecuteCommandParams, client LspClient, clientInfo *LanguageServerClientInfo) (*protocol.ApplyWorkspaceEditParams, error) { 348 | lsLogger := globalLsLogger.With().Str("code_action", "sage.completion.selection").Logger() 349 | argBs, err := json.Marshal(params.Arguments[0]) 350 | if err != nil { 351 | return nil, err 352 | } 353 | 354 | args := &LlmCompletionArgs{} 355 | err = json.Unmarshal(argBs, args) 356 | if err != nil { 357 | return nil, err 358 | } 359 | 360 | prompt, err := buildPrompt(lsLogger, args, clientInfo) 361 | if err != nil { 362 | return nil, err 363 | } 364 | 365 | completionCh := make(chan string) 366 | errCh := make(chan error) 367 | 368 | var receiveCompletionFunc GenerateResponseFunc = func(cr CompletionResponse) error { 369 | lsLogger.Debug().Str("text", cr.Text).Bool("done", cr.Done).Msg("Received text") 370 | completionCh <- cr.Text 371 | if cr.Done { 372 | close(completionCh) 373 | } 374 | 375 | return nil 376 | } 377 | 378 | models, err := clientInfo.Config.Models.Get() 379 | if err != nil { 380 | return nil, err 381 | } 382 | 383 | model := *models.Default 384 | 385 | lsLogger.Info().Str("model", model).Str("prompt", prompt).Msg("Generating completion") 386 | 387 | client.Progress(context.TODO(), &protocol.ProgressParams{ 388 | // Token: *params.WorkDoneProgressParams.WorkDoneToken, 389 | Value: &protocol.WorkDoneProgressBegin{ 390 | Kind: protocol.WorkDoneProgressKindBegin, 391 | Title: "Sage completion", 392 | Message: "connecting...", 393 | }, 394 | }) 395 | 396 | go func() { 397 | err := clientInfo.LLM.StreamCompletion(context.TODO(), model, prompt, receiveCompletionFunc) 398 | if err != nil { 399 | errCh <- err 400 | } 401 | 402 | close(errCh) 403 | }() 404 | 405 | editsManager := NewLlmResponseEditsManager(client, args.Filename, args.Selection.End.Line, args.Selection.End.Character) 406 | 407 | outer: 408 | for { 409 | select { 410 | case nextText, ok := <-completionCh: 411 | if !ok { 412 | break outer 413 | } 414 | 415 | editsManager.NextEdit(nextText) 416 | 417 | case err, ok := <-errCh: 418 | if !ok { 419 | continue 420 | } 421 | 422 | close(completionCh) 423 | 424 | return nil, err 425 | } 426 | } 427 | 428 | lsLogger.Debug().Str("completion", editsManager.fullText).Msg("Returning completion") 429 | client.Progress(context.TODO(), &protocol.ProgressParams{ 430 | // Token: *params.WorkDoneProgressParams.WorkDoneToken, 431 | Value: &protocol.WorkDoneProgressEnd{ 432 | Kind: protocol.WorkDoneProgressKindEnd, 433 | Message: "Done!", 434 | }, 435 | }) 436 | 437 | return nil, nil 438 | }, 439 | } 440 | -------------------------------------------------------------------------------- /language_server_cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/sha1" 6 | "encoding/json" 7 | "fmt" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | "strings" 12 | "sync" 13 | "time" 14 | 15 | "github.com/everestmz/sage/docstate" 16 | "github.com/everestmz/sage/lsp" 17 | "github.com/everestmz/sage/rpc/server" 18 | "github.com/rs/zerolog" 19 | "github.com/spf13/cobra" 20 | "go.lsp.dev/jsonrpc2" 21 | "go.lsp.dev/protocol" 22 | "go.lsp.dev/uri" 23 | "go.uber.org/zap" 24 | "gopkg.in/natefinch/lumberjack.v2" 25 | ) 26 | 27 | type LanguageServerConfig struct { 28 | Command *string `json:"command" yaml:"command"` 29 | Args []string `json:"args" yaml:"args"` 30 | Config map[string]any `json:"config" yaml:"config"` 31 | } 32 | 33 | type ChildLanguageServer struct { 34 | protocol.Server 35 | Conn jsonrpc2.Conn 36 | Cmd *exec.Cmd 37 | Close func() 38 | Context context.Context 39 | InitResult *protocol.InitializeResult 40 | } 41 | 42 | type SageLanguageServerConfig struct { 43 | // This should be in order: i.e. first LS in the list gets the message first, 44 | // then the next one, etc etc based on capabilities 45 | // Right now, we'll just support 1 lsp 46 | LanguageServers []*LanguageServerConfig `json:"language_servers"` 47 | } 48 | 49 | var globalLsLogger = zerolog.New(&lumberjack.Logger{ 50 | Filename: filepath.Join(getConfigDir(), "sage.log"), 51 | MaxSize: 50, 52 | MaxBackups: 10, 53 | }).Level(zerolog.DebugLevel).With().Timestamp().Logger() 54 | 55 | func init() { 56 | flags := LanguageServerCmd.PersistentFlags() 57 | 58 | flags.String("cmd", "", "Specify the command (and arguments) to run, as a single space-separated string") 59 | } 60 | 61 | var _ jsonrpc2.Conn = &LspConnLogger{} 62 | 63 | type LspConnLogger struct { 64 | conn jsonrpc2.Conn 65 | log zerolog.Logger 66 | } 67 | 68 | // Call implements jsonrpc2.Conn. 69 | func (l *LspConnLogger) Call(ctx context.Context, method string, params interface{}, result interface{}) (jsonrpc2.ID, error) { 70 | paramsBs, err := json.Marshal(params) 71 | if err != nil { 72 | return jsonrpc2.ID{}, err 73 | } 74 | 75 | logMsg := l.log.Info(). 76 | Str("method", method). 77 | RawJSON("params", paramsBs) 78 | 79 | msgId, err := l.conn.Call(ctx, method, params, result) 80 | if err != nil { 81 | logMsg = logMsg.AnErr("err", err) 82 | } else { 83 | resultBs, err := json.Marshal(params) 84 | if err != nil { 85 | return jsonrpc2.ID{}, err 86 | } 87 | 88 | logMsg = logMsg.RawJSON("result", resultBs) 89 | } 90 | 91 | logMsg.Msg("Call") 92 | 93 | return msgId, err 94 | } 95 | 96 | // Close implements jsonrpc2.Conn. 97 | func (l *LspConnLogger) Close() error { 98 | return l.conn.Close() 99 | } 100 | 101 | // Done implements jsonrpc2.Conn. 102 | func (l *LspConnLogger) Done() <-chan struct{} { 103 | return l.conn.Done() 104 | } 105 | 106 | // Err implements jsonrpc2.Conn. 107 | func (l *LspConnLogger) Err() error { 108 | return l.conn.Err() 109 | } 110 | 111 | // Go implements jsonrpc2.Conn. 112 | func (l *LspConnLogger) Go(ctx context.Context, handler jsonrpc2.Handler) { 113 | l.conn.Go(ctx, handler) 114 | } 115 | 116 | // Notify implements jsonrpc2.Conn. 117 | func (l *LspConnLogger) Notify(ctx context.Context, method string, params interface{}) error { 118 | paramsBs, err := json.Marshal(params) 119 | if err != nil { 120 | return err 121 | } 122 | 123 | logMsg := l.log.Info(). 124 | Str("method", method). 125 | RawJSON("params", paramsBs) 126 | 127 | err = l.conn.Notify(ctx, method, params) 128 | if err != nil { 129 | logMsg = logMsg.AnErr("err", err) 130 | } 131 | 132 | logMsg.Msg("Notify") 133 | return err 134 | } 135 | 136 | type LspClient struct { 137 | protocol.Client 138 | 139 | conn jsonrpc2.Conn 140 | } 141 | 142 | func (c *LspClient) Conn() jsonrpc2.Conn { 143 | return c.conn 144 | } 145 | 146 | var LanguageServerCmd = &cobra.Command{ 147 | Use: "ls", 148 | Short: "Start the sage language server", 149 | RunE: func(cmd *cobra.Command, args []string) error { 150 | flags := cmd.Flags() 151 | 152 | lspCommand, err := flags.GetString("cmd") 153 | if err != nil { 154 | return err 155 | } 156 | 157 | rwc := &CombinedReadWriteCloser{ 158 | Reader: os.Stdin, 159 | Writer: os.Stdout, 160 | Closer: func() error { 161 | return nil 162 | }, 163 | } 164 | 165 | // I don't know if we actually need this, but this is a way for 166 | // us to close the connection from within the dispatcher/handler 167 | closeChan := make(chan bool) 168 | 169 | ctx := context.TODO() 170 | stream := jsonrpc2.NewStream(rwc) 171 | conn := jsonrpc2.NewConn(stream) 172 | client := LspClient{ 173 | Client: protocol.ClientDispatcher(conn, zap.L()), 174 | conn: conn, 175 | } 176 | ctx = protocol.WithClient(ctx, client) 177 | 178 | lspCommandSplit := strings.Split(lspCommand, " ") 179 | 180 | config, err := getConfigForWd() 181 | if err != nil { 182 | return err 183 | } 184 | 185 | // Taking a page out of protocol.NewServer 186 | dispatcher, err := GetLanguageServerDispatcher(closeChan, client, lspCommandSplit, config) 187 | if err != nil { 188 | return err 189 | } 190 | 191 | conn.Go( 192 | ctx, protocol.Handlers( 193 | dispatcher, 194 | ), 195 | ) 196 | 197 | select { 198 | case <-closeChan: 199 | err := conn.Close() 200 | if err != nil { 201 | return err 202 | } 203 | case <-conn.Done(): 204 | close(closeChan) 205 | } 206 | 207 | return nil 208 | }, 209 | } 210 | 211 | func NewLanguageServerClientInfo(config *SagePathConfig, llm *LLMClient) *LanguageServerClientInfo { 212 | wd, err := os.Getwd() 213 | if err != nil { 214 | panic(err) 215 | } 216 | 217 | db, err := openDB(wd) 218 | if err != nil { 219 | panic(err) 220 | } 221 | 222 | docs := docstate.NewDocumentState() 223 | 224 | server.StartStateServer(docs, getWorkspaceSocketPath(wd)) 225 | 226 | return &LanguageServerClientInfo{ 227 | Docs: docs, 228 | 229 | LLM: llm, 230 | Config: config, 231 | 232 | stateDir: filepath.Join(getConfigDir(), "state"), 233 | db: db, 234 | wd: wd, 235 | } 236 | } 237 | 238 | type LanguageServerClientInfo struct { 239 | Docs *docstate.DocumentState 240 | 241 | LLM *LLMClient 242 | Config *SagePathConfig 243 | 244 | stateDir string 245 | db *DB 246 | wd string 247 | } 248 | 249 | func (ci *LanguageServerClientInfo) GetSymbol(filename string, symbol string) (string, error) { 250 | symbols, err := ci.db.FindSymbolByPrefix(symbol) 251 | if err != nil { 252 | return "", err 253 | } 254 | 255 | // XXX: there be dragons here! If a file is modified but the new modifications haven't been indexed, 256 | // then we're in for trouble, since the ranges will be wrong! Maybe ok if we cache at a higher level. 257 | // Maybe we need to build in some reindexing into our LSP - either that, or we need to cache the symbol 258 | // text in the DB, but that'd probably grow the size too much. 259 | 260 | for _, sym := range symbols { 261 | if strings.HasSuffix(sym.Location.URI.Filename(), filename) { 262 | fileContent, err := ci.GetFile(filename) 263 | if err != nil { 264 | return "", err 265 | } 266 | symbolText := lsp.GetRangeFromFile(fileContent, sym.Location.Range) 267 | return symbolText, nil 268 | } 269 | } 270 | 271 | return "", fmt.Errorf("Symbol '%s' not found for filename '%s' - check naming", symbol, filename) 272 | } 273 | 274 | func (ci *LanguageServerClientInfo) GetFile(filename string) (string, error) { 275 | if openDoc, ok := ci.Docs.GetOpenDocument(uri.File(filename)); ok { 276 | return openDoc.Text, nil 277 | } 278 | 279 | fileBytes, err := os.ReadFile(filepath.Join(ci.wd, filename)) 280 | return string(fileBytes), err 281 | } 282 | 283 | func (ci *LanguageServerClientInfo) GetRange(filename string, start, end int) (string, error) { 284 | fileContent, err := ci.GetFile(filename) 285 | if err != nil { 286 | return "", err 287 | } 288 | 289 | // Convert to indices 290 | start = start - 1 291 | if start < 0 { 292 | start = 0 293 | } 294 | 295 | lines := strings.Split(fileContent, "\n") 296 | 297 | if start >= len(lines) { 298 | return "", fmt.Errorf("Range start '%d' > length of file (%d lines)", start, len(lines)) 299 | } 300 | 301 | if end >= len(lines) { 302 | end = len(lines) - 1 303 | } 304 | 305 | return strings.Join(lines[start:end], "\n"), nil 306 | } 307 | 308 | // func (ci *LanguageServerClientInfo) updateState(uri uri.URI) { 309 | // path := filepath.Join(ci.stateDir, uri.Filename()) 310 | // err := os.MkdirAll(filepath.Dir(path), 0755) 311 | // if err != nil { 312 | // panic(err) 313 | // } 314 | // err = os.WriteFile( 315 | // path, 316 | // []byte(ci.openDocuments[uri].Text), 317 | // 0755, 318 | // ) 319 | // if err != nil { 320 | // panic(err) 321 | // } 322 | // } 323 | 324 | // func (ci *LanguageServerClientInfo) clearState(uri uri.URI) { 325 | // err := os.RemoveAll(filepath.Join(ci.stateDir, uri.Filename())) 326 | // if err != nil { 327 | // panic(err) 328 | // } 329 | // } 330 | 331 | type CommandDefinition struct { 332 | Title string 333 | Identifier string 334 | ShowCodeAction bool 335 | BuildArgs func(params *protocol.CodeActionParams) (args []any, err error) 336 | Execute func(params *protocol.ExecuteCommandParams, client LspClient, clientInfo *LanguageServerClientInfo) (*protocol.ApplyWorkspaceEditParams, error) 337 | } 338 | 339 | func (cd *CommandDefinition) BuildDefinition(params *protocol.CodeActionParams) (*protocol.Command, error) { 340 | args, err := cd.BuildArgs(params) 341 | if err != nil { 342 | return nil, err 343 | } 344 | 345 | return &protocol.Command{ 346 | Title: cd.Title, 347 | Command: cd.Identifier, 348 | Arguments: args, 349 | }, nil 350 | } 351 | 352 | type LlmCompletionArgs struct { 353 | Filename uri.URI 354 | Selection protocol.Range 355 | } 356 | 357 | func findSymbol(ctx context.Context, db *DB, llm *LLMClient, query string) ([]protocol.SymbolInformation, error) { 358 | symbols, err := db.FindSymbolByPrefix(query) 359 | if err != nil { 360 | return nil, err 361 | } 362 | if len(symbols) > 0 { 363 | return symbols, nil 364 | } 365 | 366 | if len(query) < 10 || !strings.Contains(query, " ") { // This heuristic is hacky, but it should stop basic issues 367 | return nil, nil 368 | } 369 | 370 | // We're getting no symbols from the DB and our query is long enough 371 | // Time to try a semantic search 372 | embedding, err := llm.GetEmbedding(ctx, "nomic-embed-text", query) 373 | if err != nil { 374 | return nil, err 375 | } 376 | 377 | return db.FindSymbolByEmbedding(embedding) 378 | } 379 | 380 | func isNotification(method string) bool { 381 | switch method { 382 | case protocol.MethodInitialized: 383 | fallthrough 384 | case protocol.MethodExit: 385 | fallthrough 386 | case protocol.MethodWorkDoneProgressCancel: 387 | fallthrough 388 | case protocol.MethodLogTrace: 389 | fallthrough 390 | case protocol.MethodSetTrace: 391 | fallthrough 392 | case protocol.MethodTextDocumentDidChange: 393 | fallthrough 394 | case protocol.MethodWorkspaceDidChangeConfiguration: 395 | fallthrough 396 | case protocol.MethodWorkspaceDidChangeWatchedFiles: 397 | fallthrough 398 | case protocol.MethodWorkspaceDidChangeWorkspaceFolders: 399 | fallthrough 400 | case protocol.MethodTextDocumentDidClose: 401 | fallthrough 402 | case protocol.MethodTextDocumentDidOpen: 403 | fallthrough 404 | case protocol.MethodTextDocumentDidSave: 405 | fallthrough 406 | case protocol.MethodTextDocumentWillSave: 407 | fallthrough 408 | case protocol.MethodDidCreateFiles: 409 | fallthrough 410 | case protocol.MethodDidRenameFiles: 411 | fallthrough 412 | case protocol.MethodDidDeleteFiles: 413 | return true 414 | default: 415 | return false 416 | } 417 | } 418 | 419 | func GetLanguageServerDispatcher(closeChan chan bool, clientConn LspClient, lsCommand []string, config *SagePathConfig) (func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error, error) { 420 | llm, err := NewLLMClient() 421 | if err != nil { 422 | return nil, err 423 | } 424 | 425 | clientInfo := NewLanguageServerClientInfo(config, llm) 426 | var ls *ChildLanguageServer 427 | 428 | logLock := &sync.Mutex{} 429 | 430 | // var l *locality.Locality 431 | 432 | handler := func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { 433 | logLock.Lock() 434 | defer logLock.Unlock() 435 | hasher := sha1.New() 436 | hasher.Write(req.Params()) 437 | lsLogger := globalLsLogger.With().Str("method", req.Method()).Str("request_hash", fmt.Sprintf("%x", hasher.Sum(nil))).Int("request_timestamp", int(time.Now().UnixNano())).Logger() 438 | lsLogger.Info().RawJSON("params", req.Params()).Msg("Received message from client") 439 | 440 | switch req.Method() { 441 | case protocol.MethodInitialize: 442 | params := &protocol.InitializeParams{} 443 | err = json.Unmarshal(req.Params(), params) 444 | if err != nil { 445 | return err 446 | } 447 | 448 | params.ProcessID = int32(os.Getpid()) 449 | ls, err = startLsp(&LanguageServerConfig{ 450 | Command: &lsCommand[0], 451 | Args: lsCommand[1:], 452 | }, &clientConn.Client, params) 453 | if err != nil { 454 | return err 455 | } 456 | 457 | capabilitiesJson, err := json.Marshal(ls.InitResult) 458 | if err != nil { 459 | return err 460 | } 461 | lsLogger.Info(). 462 | RawJSON("lsp_init_result", capabilitiesJson). 463 | RawJSON("lsp_config", req.Params()). 464 | Str("command", ls.Cmd.String()). 465 | Msg("Started LSP") 466 | 467 | // We need to add our own capabilities in here 468 | if ls.InitResult.Capabilities.ExecuteCommandProvider == nil { 469 | ls.InitResult.Capabilities.ExecuteCommandProvider = &protocol.ExecuteCommandOptions{} 470 | } 471 | 472 | for _, cmd := range lspCommands { 473 | ls.InitResult.Capabilities.ExecuteCommandProvider.Commands = append(ls.InitResult.Capabilities.ExecuteCommandProvider.Commands, cmd.Identifier) 474 | } 475 | 476 | // We can do symbol search 477 | ls.InitResult.Capabilities.WorkspaceSymbolProvider = true 478 | 479 | return reply(ctx, ls.InitResult, nil) 480 | 481 | // case protocol.MethodWorkspaceDidChangeConfiguration: 482 | // params := &protocol.DidChangeConfigurationParams{} 483 | // err := json.Unmarshal(req.Params(), params) 484 | // if err != nil { 485 | // return err 486 | // } 487 | 488 | // return reply(ctx, nil, ls.DidChangeConfiguration(ctx, params)) 489 | 490 | case protocol.MethodInitialized: 491 | // XXX: pretty sure we need to send initialized through to the client 492 | // but we should verify if this breaks pyright since it may 493 | // return reply(ctx, nil, nil) 494 | 495 | case protocol.MethodTextDocumentDidOpen: 496 | params := &protocol.DidOpenTextDocumentParams{} 497 | err := json.Unmarshal(req.Params(), params) 498 | if err != nil { 499 | return err 500 | } 501 | 502 | clientInfo.Docs.OpenDocument(¶ms.TextDocument) 503 | 504 | return reply(ctx, nil, ls.DidOpen(ctx, params)) 505 | 506 | case protocol.MethodTextDocumentDidClose: 507 | params := &protocol.DidCloseTextDocumentParams{} 508 | err := json.Unmarshal(req.Params(), params) 509 | if err != nil { 510 | return err 511 | } 512 | 513 | clientInfo.Docs.CloseDocument(params.TextDocument.URI) 514 | 515 | return reply(ctx, nil, ls.DidClose(ctx, params)) 516 | 517 | case protocol.MethodTextDocumentDidChange: 518 | params := &protocol.DidChangeTextDocumentParams{} 519 | err := json.Unmarshal(req.Params(), params) 520 | if err != nil { 521 | return err 522 | } 523 | 524 | err = clientInfo.Docs.EditDocument(params.TextDocument.URI, func(doc *protocol.TextDocumentItem) error { 525 | newText, err := applyChangesToDocument(doc.Text, params.ContentChanges) 526 | if err != nil { 527 | lsLogger.Error().Err(err).Msg("Error applying edits") 528 | return err 529 | } 530 | 531 | doc.Text = newText 532 | lsLogger.Info().Str("after_edit", newText).Msg("After edits applied") 533 | return nil 534 | }) 535 | if err != nil { 536 | return err 537 | } 538 | 539 | // No reply from us - pass to child lsp 540 | return reply(ctx, nil, ls.DidChange(ctx, params)) 541 | 542 | case protocol.MethodWorkspaceSymbol: 543 | params := &protocol.WorkspaceSymbolParams{} 544 | err := json.Unmarshal(req.Params(), params) 545 | if err != nil { 546 | return err 547 | } 548 | 549 | symbols, err := clientInfo.db.FindSymbolByPrefix(params.Query) 550 | if err != nil { 551 | return err 552 | } 553 | 554 | return reply(ctx, symbols, err) 555 | 556 | case protocol.MethodTextDocumentCodeAction: 557 | params := &protocol.CodeActionParams{} 558 | err := json.Unmarshal(req.Params(), params) 559 | if err != nil { 560 | return err 561 | } 562 | 563 | resp := []protocol.CodeAction{} 564 | for _, cmd := range lspCommands { 565 | args, err := cmd.BuildArgs(params) 566 | if err != nil { 567 | return err 568 | } 569 | 570 | if cmd.ShowCodeAction { 571 | resp = append(resp, protocol.CodeAction{ 572 | Title: "Sage: " + cmd.Title, 573 | // XXX: is this correct? idk 574 | Kind: protocol.Refactor, 575 | Command: &protocol.Command{ 576 | Title: cmd.Title, 577 | Command: cmd.Identifier, 578 | Arguments: args, 579 | }, 580 | }) 581 | } 582 | } 583 | 584 | childActions, err := ls.CodeAction(ctx, params) 585 | if err != nil { 586 | return err 587 | } 588 | 589 | return reply(ctx, append(resp, childActions...), nil) 590 | case protocol.MethodWorkspaceExecuteCommand: 591 | params := &protocol.ExecuteCommandParams{} 592 | err := json.Unmarshal(req.Params(), params) 593 | if err != nil { 594 | return err 595 | } 596 | 597 | var cmd *CommandDefinition 598 | for _, def := range lspCommands { 599 | if def.Identifier == params.Command { 600 | cmd = def 601 | break 602 | } 603 | } 604 | 605 | if cmd == nil { 606 | // We just want to pass through to the child 607 | break 608 | } 609 | 610 | reply(ctx, []any{}, err) 611 | 612 | edit, err := cmd.Execute(params, clientConn, clientInfo) 613 | if err != nil { 614 | return err 615 | } 616 | 617 | if edit != nil { 618 | ok, err := clientConn.ApplyEdit(ctx, edit) 619 | if err != nil { 620 | // Error in LSP implementation we should fix 621 | if err.Error() != "unmarshaling result: json: cannot unmarshal \"{\\\"applied\\\":true}\" into Go value of type bool" { 622 | return err 623 | } 624 | } 625 | 626 | lsLogger.Debug().Bool("apply_edit_result", ok).Msg("Result of applying edit") 627 | } 628 | 629 | return err 630 | 631 | case protocol.MethodTextDocumentHover: 632 | params := &protocol.HoverParams{} 633 | err := json.Unmarshal(req.Params(), params) 634 | if err != nil { 635 | return err 636 | } 637 | 638 | // fileName := params.TextDocument.URI.Filename() 639 | // fileContent, err := clientInfo.GetFile(fileName) 640 | // if err != nil { 641 | // return err 642 | // } 643 | 644 | // TODO: re-add locality 645 | // go func() { 646 | // _, err := l.GetContext(fileName, fileContent, int(params.Position.Line)) 647 | // if err != nil { 648 | // log.Error().Err(err).Msg("Error getting locality context") 649 | // } 650 | // }() 651 | 652 | // no return, pass through 653 | 654 | case protocol.MethodExit: 655 | fallthrough 656 | case protocol.MethodShutdown: 657 | // We kill the servers: (this will happen in the passthrough) 658 | ls.Close() 659 | 660 | // And then kill the connection to the parent 661 | defer close(closeChan) 662 | } 663 | 664 | // We pass through to the language server 665 | 666 | var result any = nil 667 | if isNotification(req.Method()) { 668 | lsLogger.Info().Msg("Passing through to child as notification") 669 | ls.Conn.Notify(ctx, req.Method(), req.Params()) 670 | } else { 671 | lsLogger.Info().Msg("Passing through to child as method") 672 | result, err = ls.Server.Request(ctx, req.Method(), req.Params()) 673 | } 674 | if err != nil { 675 | if jrpcErr, ok := err.(*jsonrpc2.Error); ok { 676 | errLog := lsLogger.Info(). 677 | Int32("code", int32(jrpcErr.Code)). 678 | Str("message", jrpcErr.Message). 679 | Err(err) 680 | 681 | if ls.Cmd.ProcessState != nil { 682 | errLog = errLog. 683 | Bool("cmd_exited", ls.Cmd.ProcessState.Exited()). 684 | Bool("cmd_success", ls.Cmd.ProcessState.Success()). 685 | Int("cmd_exit_code", ls.Cmd.ProcessState.ExitCode()) 686 | } 687 | 688 | if jrpcErr.Data != nil { 689 | errLog = errLog.RawJSON("data", *jrpcErr.Data) 690 | } 691 | errLog.Msg("JsonRPC error response from child") 692 | } else { 693 | lsLogger.Info().Err(err).Type("err_type", err).Msg("Error response from child") 694 | } 695 | return reply(ctx, nil, err) 696 | } 697 | 698 | if result != nil { 699 | resp_bs, err := json.Marshal(result) 700 | if err != nil { 701 | lsLogger.Info().Err(err).Msg("Error marshaling response from child") 702 | return reply(ctx, nil, err) 703 | } 704 | lsLogger.Info().Str("resp", string(resp_bs)).Msg("Response from child") 705 | } else { 706 | lsLogger.Info().Msg("Empty response from child") 707 | } 708 | return reply(ctx, result, err) 709 | } 710 | 711 | return func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { 712 | err := handler(ctx, reply, req) 713 | if err != nil { 714 | globalLsLogger.Error().Str("method", req.Method()).Err(err).Msg("Error handling request") 715 | } 716 | return err 717 | }, nil 718 | } 719 | 720 | func applyChangesToDocument(textDocument string, changes []protocol.TextDocumentContentChangeEvent) (string, error) { 721 | lines := strings.Split(textDocument, "\n") 722 | 723 | for _, change := range changes { 724 | start := change.Range.Start 725 | end := change.Range.End 726 | 727 | if start.Line >= uint32(len(lines)) || end.Line >= uint32(len(lines)) { 728 | return "", fmt.Errorf("invalid range: start or end line out of bounds") 729 | } 730 | 731 | // Split the lines that will be modified 732 | if start.Character > uint32(len(lines[start.Line])) { 733 | start.Character = uint32(len(lines[start.Line])) 734 | } 735 | 736 | // Handle single line changes 737 | if start.Line == end.Line { 738 | line := lines[start.Line] 739 | newLine := line[:start.Character] + change.Text + line[end.Character:] 740 | lines[start.Line] = newLine 741 | } else { 742 | // Handle multi-line changes 743 | startLine := lines[start.Line][:start.Character] + change.Text 744 | endLine := lines[end.Line][end.Character:] 745 | 746 | newLines := strings.Split(startLine, "\n") 747 | newLines[len(newLines)-1] += endLine 748 | 749 | // Replace the affected lines 750 | lines = append(lines[:start.Line], append(newLines, lines[end.Line+1:]...)...) 751 | } 752 | } 753 | 754 | return strings.Join(lines, "\n"), nil 755 | } 756 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | connectrpc.com/connect v1.17.0 h1:W0ZqMhtVzn9Zhn2yATuUokDLO5N+gIuBWMOnsQrfmZk= 4 | connectrpc.com/connect v1.17.0/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= 5 | dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= 6 | dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 7 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 8 | gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= 9 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 10 | github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= 11 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 12 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 13 | github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= 14 | github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= 15 | github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= 16 | github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= 17 | github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= 18 | github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= 19 | github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= 20 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 21 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 22 | github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 h1:q4dksr6ICHXqG5hm0ZW5IHyeEJXoIJSOZeBLmWPNeIQ= 23 | github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs= 24 | github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= 25 | github.com/asg017/sqlite-vec/bindings/go/cgo v0.0.0-20240628232741-344213161399 h1:ittCy1C+KwAzBNaP62ASsyC+h3QVoeQz43BaDC6VhNQ= 26 | github.com/asg017/sqlite-vec/bindings/go/cgo v0.0.0-20240628232741-344213161399/go.mod h1:Go89G54PaautWRwxvAa1fmKeYoSuUyIvSYpvlfXQaNU= 27 | github.com/asg017/sqlite-vss/bindings/go v0.0.0-20240505200016-8d3c6ff2fca6 h1:zOexzTeDtpA1r7zEzTEdbRC7IxYVCWkWPvDtX3+n090= 28 | github.com/asg017/sqlite-vss/bindings/go v0.0.0-20240505200016-8d3c6ff2fca6/go.mod h1:BlTaV0F1bZZ/7eHyb+MtcDxB/7aGrnD1yulja1lBXiM= 29 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 30 | github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= 31 | github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= 32 | github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= 33 | github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= 34 | github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= 35 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 36 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 37 | github.com/chewxy/hm v1.0.0 h1:zy/TSv3LV2nD3dwUEQL2VhXeoXbb9QkpmdRAVUFiA6k= 38 | github.com/chewxy/hm v1.0.0/go.mod h1:qg9YI4q6Fkj/whwHR1D+bOGeF7SniIP40VweVepLjg0= 39 | github.com/chewxy/math32 v1.0.0/go.mod h1:Miac6hA1ohdDUTagnvJy/q+aNnEk16qWUdb8ZVhvCN0= 40 | github.com/chewxy/math32 v1.10.1 h1:LFpeY0SLJXeaiej/eIp2L40VYfscTvKh/FSEZ68uMkU= 41 | github.com/chewxy/math32 v1.10.1/go.mod h1:dOB2rcuFrCn6UHrze36WSLVPKtzPMRAQvBvUwkSsLqs= 42 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 43 | github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= 44 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 45 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 46 | github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= 47 | github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 48 | github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= 49 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 50 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 51 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 52 | github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 53 | github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= 54 | github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= 55 | github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= 56 | github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= 57 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 58 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 59 | github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= 60 | github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= 61 | github.com/d4l3k/go-bfloat16 v0.0.0-20211005043715-690c3bdd05f1 h1:cBzrdJPAFBsgCrDPnZxlp1dF2+k4r1kVpD7+1S1PVjY= 62 | github.com/d4l3k/go-bfloat16 v0.0.0-20211005043715-690c3bdd05f1/go.mod h1:uw2gLcxEuYUlAd/EXyjc/v55nd3+47YAgWbSXVxPrNI= 63 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 64 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 65 | github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= 66 | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= 67 | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 68 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 69 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 70 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 71 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 72 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 73 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= 74 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 75 | github.com/everestmz/cursor-rpc v0.0.0-20241202041540-8dd67a7b9804 h1:AhJclW6796wt8wbjfCsYZVZq0ff8bcbsuvrxavIJ+BI= 76 | github.com/everestmz/cursor-rpc v0.0.0-20241202041540-8dd67a7b9804/go.mod h1:KhWBNw3NYM9PXGfABP2cgbBmPLA9cfpF7eb2e+Vcyxs= 77 | github.com/everestmz/llmcat v0.0.4 h1:NPK9/7XkWvx0F3/8a+poSG1AWrrL6IzgfMFgru4vxa4= 78 | github.com/everestmz/llmcat v0.0.4/go.mod h1:ie25vqmlvOZZga5VrutlCtKCGK7eK0RjHUdRUIAT4/o= 79 | github.com/everestmz/llmcat v0.0.5 h1:EWgJomBy4nkEIQ9LTOQVg4umddbAVli20dma8BOP40Q= 80 | github.com/everestmz/llmcat v0.0.5/go.mod h1:ie25vqmlvOZZga5VrutlCtKCGK7eK0RjHUdRUIAT4/o= 81 | github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= 82 | github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= 83 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 84 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 85 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 86 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 87 | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= 88 | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= 89 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 90 | github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw= 91 | github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E= 92 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 93 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 94 | github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= 95 | github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 96 | github.com/glycerine/blake2b v0.0.0-20151022103502-3c8c640cd7be h1:XBJdPGgA3qqhW+p9CANCAVdF7ZIXdu3pZAkypMkKAjE= 97 | github.com/glycerine/blake2b v0.0.0-20151022103502-3c8c640cd7be/go.mod h1:OSCrScrFAjcBObrulk6BEQlytA462OkG1UGB5NYj9kE= 98 | github.com/glycerine/greenpack v5.1.1+incompatible h1:fDr9i6MkSGZmAy4VXPfJhW+SyK2/LNnzIp5nHyDiaIM= 99 | github.com/glycerine/greenpack v5.1.1+incompatible/go.mod h1:us0jVISAESGjsEuLlAfCd5nkZm6W6WQF18HPuOecIg4= 100 | github.com/glycerine/liner v0.0.0-20160121172638-72909af234e0 h1:4ZegphJXBTc4uFQ08UVoWYmQXorGa+ipXetUj83sMBc= 101 | github.com/glycerine/liner v0.0.0-20160121172638-72909af234e0/go.mod h1:AqJLs6UeoC65dnHxyCQ6MO31P5STpjcmgaANAU+No8Q= 102 | github.com/glycerine/zygomys v5.1.2+incompatible h1:jmcdmA3XPxgfOunAXFpipE9LQoUL6eX6d2mhYyjV4GE= 103 | github.com/glycerine/zygomys v5.1.2+incompatible/go.mod h1:i3SPKZpmy9dwF/3iWrXJ/ZLyzZucegwypwOmqRkUUaQ= 104 | github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= 105 | github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= 106 | github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= 107 | github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= 108 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= 109 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= 110 | github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= 111 | github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= 112 | github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= 113 | github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= 114 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 115 | github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= 116 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 117 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 118 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 119 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 120 | github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= 121 | github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 122 | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= 123 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 124 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 125 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 126 | github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I= 127 | github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= 128 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 129 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 130 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 131 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 132 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 133 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 134 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 135 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 136 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 137 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 138 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 139 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 140 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 141 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 142 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 143 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 144 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 145 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 146 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 147 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 148 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 149 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 150 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 151 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 152 | github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 153 | github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= 154 | github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 155 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 156 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 157 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 158 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 159 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 160 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 161 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 162 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 163 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 164 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 165 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 166 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 167 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 168 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 169 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 170 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 171 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 172 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 173 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 174 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 175 | github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 176 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 177 | github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 178 | github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 179 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 180 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 181 | github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= 182 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 183 | github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 184 | github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 185 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 186 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 187 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 188 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 189 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 190 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 191 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 192 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 193 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 194 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 195 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 196 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 197 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 198 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 199 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= 200 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 201 | github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 202 | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 203 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 204 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 205 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 206 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 207 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 208 | github.com/nlpodyssey/gopickle v0.3.0 h1:BLUE5gxFLyyNOPzlXxt6GoHEMMxD0qhsE4p0CIQyoLw= 209 | github.com/nlpodyssey/gopickle v0.3.0/go.mod h1:f070HJ/yR+eLi5WmM1OXJEGaTpuJEUiib19olXgYha0= 210 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 211 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 212 | github.com/ollama/ollama v0.1.46 h1:C6zs4QUXC/7a9/Kb6MEioIxJWlZCStHghtslIuGbExg= 213 | github.com/ollama/ollama v0.1.46/go.mod h1:TvVa25PEZI6M0bosiW1sa2XJGq3Xw/OPlpUAkMEntTU= 214 | github.com/pdevine/tensor v0.0.0-20240510204454-f88f4562727c h1:GwiUUjKefgvSNmv3NCvI/BL0kDebW6Xa+kcdpdc1mTY= 215 | github.com/pdevine/tensor v0.0.0-20240510204454-f88f4562727c/go.mod h1:PSojXDXF7TbgQiD6kkd98IHOS0QqTyUEaWRiS8+BLu8= 216 | github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= 217 | github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 218 | github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 h1:jYi87L8j62qkXzaYHAQAhEapgukhenIMZRBKTNRLHJ4= 219 | github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= 220 | github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= 221 | github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= 222 | github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 223 | github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= 224 | github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= 225 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 226 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 227 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 228 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 229 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 230 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 231 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 232 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 233 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 234 | github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= 235 | github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= 236 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 237 | github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= 238 | github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= 239 | github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= 240 | github.com/segmentio/encoding v0.3.4 h1:WM4IBnxH8B9TakiM2QD5LyNl9JSndh88QbHqVC+Pauc= 241 | github.com/segmentio/encoding v0.3.4/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM= 242 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= 243 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 244 | github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 h1:aSISeOcal5irEhJd1M+IrApc0PdcN7e7Aj4yuEnOrfQ= 245 | github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= 246 | github.com/shurcooL/go-goon v1.0.0 h1:BCQPvxGkHHJ4WpBO4m/9FXbITVIsvAm/T66cCcCGI7E= 247 | github.com/shurcooL/go-goon v1.0.0/go.mod h1:2wTHMsGo7qnpmqA8ADYZtP4I1DD94JpXGQ3Dxq2YQ5w= 248 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 249 | github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= 250 | github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= 251 | github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 h1:6C8qej6f1bStuePVkLSFxoU22XBS165D3klxlzRg8F4= 252 | github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82/go.mod h1:xe4pgH49k4SsmkQq5OT8abwhWmnzkhpgnXeekbx2efw= 253 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 254 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 255 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 256 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 257 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 258 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 259 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 260 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 261 | github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 262 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 263 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 264 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 265 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 266 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 267 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 268 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 269 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 270 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 271 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 272 | github.com/tinylib/msgp v1.2.0 h1:0uKB/662twsVBpYUPbokj4sTSKhWFKB7LopO2kWK8lY= 273 | github.com/tinylib/msgp v1.2.0/go.mod h1:2vIGs3lcUo8izAATNobrCHevYZC/LMsJtw4JPiYPHro= 274 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 275 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 276 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 277 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 278 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 279 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 280 | github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= 281 | github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= 282 | github.com/xtgo/set v1.0.0 h1:6BCNBRv3ORNDQ7fyoJXRv+tstJz3m1JVFQErfeZz2pY= 283 | github.com/xtgo/set v1.0.0/go.mod h1:d3NHzGzSa0NmB2NhFyECA+QdRp29oEn2xbT+TpeFoM8= 284 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 285 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 286 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 287 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 288 | go.lsp.dev/jsonrpc2 v0.10.0 h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI= 289 | go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac= 290 | go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 h1:hCzQgh6UcwbKgNSRurYWSqh8MufqRRPODRBblutn4TE= 291 | go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2/go.mod h1:gtSHRuYfbCT0qnbLnovpie/WEmqyJ7T4n6VXiFMBtcw= 292 | go.lsp.dev/protocol v0.12.0 h1:tNprUI9klQW5FAFVM4Sa+AbPFuVQByWhP1ttNUAjIWg= 293 | go.lsp.dev/protocol v0.12.0/go.mod h1:Qb11/HgZQ72qQbeyPfJbu3hZBH23s1sr4st8czGeDMQ= 294 | go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo= 295 | go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I= 296 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 297 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 298 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 299 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 300 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 301 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 302 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 303 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 304 | go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= 305 | go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= 306 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 307 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 308 | go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= 309 | go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= 310 | go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= 311 | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= 312 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 h1:lGdhQUN/cnWdSH3291CUuxSEqc+AsGTiDxPP3r2J0l4= 313 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= 314 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 315 | golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= 316 | golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 317 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 318 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 319 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 320 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 321 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 322 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 323 | golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= 324 | golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= 325 | golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= 326 | golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= 327 | golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= 328 | golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= 329 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 330 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 331 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 332 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 333 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 334 | golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= 335 | golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= 336 | golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= 337 | golang.org/x/exp/typeparams v0.0.0-20221212164502-fae10dda9338 h1:2O2DON6y3XMJiQRAS1UWU+54aec2uopH3x7MAiqGW6Y= 338 | golang.org/x/exp/typeparams v0.0.0-20221212164502-fae10dda9338/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= 339 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 340 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 341 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 342 | golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 343 | golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 344 | golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 345 | golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 346 | golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 347 | golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 348 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 349 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 350 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 351 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 352 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 353 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 354 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 355 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 356 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 357 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 358 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 359 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 360 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 361 | golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= 362 | golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 363 | golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= 364 | golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 365 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 366 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 367 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 368 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 369 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 370 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 371 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 372 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 373 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 374 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 375 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 376 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 377 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 378 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 379 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 380 | golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 381 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 382 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 383 | golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= 384 | golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 385 | golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= 386 | golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= 387 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 388 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 389 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 390 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 391 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 392 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 393 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 394 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 395 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 396 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 397 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 398 | golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= 399 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 400 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 401 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 402 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 403 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 404 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 405 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 406 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 407 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 408 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 409 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 410 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 411 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 412 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 413 | golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 414 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 415 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 416 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 417 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 418 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 419 | golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 420 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 421 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 422 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 423 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 424 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 425 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 426 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 427 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 428 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 429 | golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= 430 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 431 | golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= 432 | golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 433 | golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= 434 | golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 435 | golang.org/x/telemetry v0.0.0-20240209200032-7b892fcb8a78 h1:vcVnuftN4J4UKLRcgetjzfU9FjjgXUUYUc3JhFplgV4= 436 | golang.org/x/telemetry v0.0.0-20240209200032-7b892fcb8a78/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ= 437 | golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= 438 | golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= 439 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 440 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 441 | golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= 442 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 443 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 444 | golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= 445 | golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= 446 | golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 447 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 448 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 449 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 450 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 451 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 452 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 453 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 454 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 455 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 456 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 457 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 458 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 459 | golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= 460 | golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 461 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 462 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 463 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 464 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 465 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 466 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 467 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 468 | golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 469 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 470 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 471 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 472 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 473 | golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 474 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 475 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 476 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 477 | golang.org/x/tools v0.18.1-0.20240412183611-d92ae0781217 h1:uH9jJYgeLCvblH0S+03kFO0qUDxRkbLRLFiKVVDl7ak= 478 | golang.org/x/tools v0.18.1-0.20240412183611-d92ae0781217/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= 479 | golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= 480 | golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= 481 | golang.org/x/tools/gopls v0.15.3 h1:zbdOidFrPTc8Bx0YrN5QKgJ0zCjyGi0L27sKQ/bDG5o= 482 | golang.org/x/tools/gopls v0.15.3/go.mod h1:W/lfb6hIysrnNXreqA2nHP2Qaay881XwhrSRMfsGUdQ= 483 | golang.org/x/vuln v1.0.1 h1:KUas02EjQK5LTuIx1OylBQdKKZ9jeugs+HiqO5HormU= 484 | golang.org/x/vuln v1.0.1/go.mod h1:bb2hMwln/tqxg32BNY4CcxHWtHXuYa3SbIBmtsyjxtM= 485 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 486 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 487 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 488 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 489 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 490 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 491 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 492 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= 493 | gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= 494 | gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= 495 | gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ= 496 | gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo= 497 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 498 | gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= 499 | gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= 500 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 501 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 502 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 503 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 504 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 505 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 506 | google.golang.org/genproto v0.0.0-20210630183607-d20f26d13c79/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U= 507 | google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao= 508 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= 509 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= 510 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 511 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 512 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 513 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 514 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 515 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 516 | google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 517 | google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= 518 | google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= 519 | google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= 520 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 521 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 522 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 523 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 524 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 525 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 526 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 527 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 528 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 529 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 530 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 531 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 532 | google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= 533 | google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 534 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 535 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 536 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 537 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 538 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 539 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 540 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 541 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 542 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 543 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 544 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 545 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 546 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 547 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 548 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 549 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 550 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 551 | gorgonia.org/vecf32 v0.9.0 h1:PClazic1r+JVJ1dEzRXgeiVl4g1/Hf/w+wUSqnco1Xg= 552 | gorgonia.org/vecf32 v0.9.0/go.mod h1:NCc+5D2oxddRL11hd+pCB1PEyXWOyiQxfZ/1wwhOXCA= 553 | gorgonia.org/vecf64 v0.9.0 h1:bgZDP5x0OzBF64PjMGC3EvTdOoMEcmfAh1VCUnZFm1A= 554 | gorgonia.org/vecf64 v0.9.0/go.mod h1:hp7IOWCnRiVQKON73kkC/AUMtEXyf9kGlVrtPQ9ccVA= 555 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 556 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 557 | honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8= 558 | honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= 559 | mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= 560 | mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= 561 | mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8= 562 | mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE= 563 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 564 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 565 | --------------------------------------------------------------------------------