summaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
authoradamdottv <[email protected]>2025-05-14 14:40:45 -0500
committeradamdottv <[email protected]>2025-05-14 14:40:45 -0500
commit5ea989fb74ab1c286fbdeadc9d5a2860361d4f95 (patch)
treed4de81020fb74ae740112b49cc07d63647f37bf5 /internal
parent45c778b90d2e13cbdb54b07372fede413e430055 (diff)
downloadopencode-5ea989fb74ab1c286fbdeadc9d5a2860361d4f95.tar.gz
opencode-5ea989fb74ab1c286fbdeadc9d5a2860361d4f95.zip
feat: docSymbols and workspaceSymbols tools
Diffstat (limited to 'internal')
-rw-r--r--internal/llm/agent/tools.go4
-rw-r--r--internal/llm/tools/lsp_definition.go (renamed from internal/llm/tools/definition.go)0
-rw-r--r--internal/llm/tools/lsp_diagnostics.go (renamed from internal/llm/tools/diagnostics.go)0
-rw-r--r--internal/llm/tools/lsp_doc_symbols.go204
-rw-r--r--internal/llm/tools/lsp_references.go (renamed from internal/llm/tools/references.go)0
-rw-r--r--internal/llm/tools/lsp_workspace_symbols.go162
6 files changed, 370 insertions, 0 deletions
diff --git a/internal/llm/agent/tools.go b/internal/llm/agent/tools.go
index b27ecc245..a0e835ff0 100644
--- a/internal/llm/agent/tools.go
+++ b/internal/llm/agent/tools.go
@@ -35,6 +35,8 @@ func PrimaryAgentTools(
tools.NewDiagnosticsTool(lspClients),
tools.NewDefinitionTool(lspClients),
tools.NewReferencesTool(lspClients),
+ tools.NewDocSymbolsTool(lspClients),
+ tools.NewWorkspaceSymbolsTool(lspClients),
NewAgentTool(sessions, messages, lspClients),
}, mcpTools...,
)
@@ -48,5 +50,7 @@ func TaskAgentTools(lspClients map[string]*lsp.Client) []tools.BaseTool {
tools.NewViewTool(lspClients),
tools.NewDefinitionTool(lspClients),
tools.NewReferencesTool(lspClients),
+ tools.NewDocSymbolsTool(lspClients),
+ tools.NewWorkspaceSymbolsTool(lspClients),
}
}
diff --git a/internal/llm/tools/definition.go b/internal/llm/tools/lsp_definition.go
index e7dc50425..e7dc50425 100644
--- a/internal/llm/tools/definition.go
+++ b/internal/llm/tools/lsp_definition.go
diff --git a/internal/llm/tools/diagnostics.go b/internal/llm/tools/lsp_diagnostics.go
index a1ed33b6a..a1ed33b6a 100644
--- a/internal/llm/tools/diagnostics.go
+++ b/internal/llm/tools/lsp_diagnostics.go
diff --git a/internal/llm/tools/lsp_doc_symbols.go b/internal/llm/tools/lsp_doc_symbols.go
new file mode 100644
index 000000000..243cb1918
--- /dev/null
+++ b/internal/llm/tools/lsp_doc_symbols.go
@@ -0,0 +1,204 @@
+package tools
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "github.com/sst/opencode/internal/lsp"
+ "github.com/sst/opencode/internal/lsp/protocol"
+)
+
+type DocSymbolsParams struct {
+ FilePath string `json:"file_path"`
+}
+
+type docSymbolsTool struct {
+ lspClients map[string]*lsp.Client
+}
+
+const (
+ DocSymbolsToolName = "docSymbols"
+ docSymbolsDescription = `Get document symbols for a file.
+WHEN TO USE THIS TOOL:
+- Use when you need to understand the structure of a file
+- Helpful for finding classes, functions, methods, and variables in a file
+- Great for getting an overview of a file's organization
+
+HOW TO USE:
+- Provide the path to the file to get symbols for
+- Results show all symbols defined in the file with their kind and location
+
+FEATURES:
+- Lists all symbols in a hierarchical structure
+- Shows symbol types (function, class, variable, etc.)
+- Provides location information for each symbol
+- Organizes symbols by their scope and relationship
+
+LIMITATIONS:
+- Requires a functioning LSP server for the file type
+- Results depend on the accuracy of the LSP server
+- May not work for all file types
+
+TIPS:
+- Use to quickly understand the structure of a large file
+- Combine with Definition and References tools for deeper code exploration
+`
+)
+
+func NewDocSymbolsTool(lspClients map[string]*lsp.Client) BaseTool {
+ return &docSymbolsTool{
+ lspClients,
+ }
+}
+
+func (b *docSymbolsTool) Info() ToolInfo {
+ return ToolInfo{
+ Name: DocSymbolsToolName,
+ Description: docSymbolsDescription,
+ Parameters: map[string]any{
+ "file_path": map[string]any{
+ "type": "string",
+ "description": "The path to the file to get symbols for",
+ },
+ },
+ Required: []string{"file_path"},
+ }
+}
+
+func (b *docSymbolsTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) {
+ var params DocSymbolsParams
+ if err := json.Unmarshal([]byte(call.Input), &params); err != nil {
+ return NewTextErrorResponse(fmt.Sprintf("error parsing parameters: %s", err)), nil
+ }
+
+ lsps := b.lspClients
+
+ if len(lsps) == 0 {
+ return NewTextResponse("\nLSP clients are still initializing. Document symbols lookup will be available once they're ready.\n"), nil
+ }
+
+ // Ensure file is open in LSP
+ notifyLspOpenFile(ctx, params.FilePath, lsps)
+
+ output := getDocumentSymbols(ctx, params.FilePath, lsps)
+
+ return NewTextResponse(output), nil
+}
+
+func getDocumentSymbols(ctx context.Context, filePath string, lsps map[string]*lsp.Client) string {
+ var results []string
+
+ for lspName, client := range lsps {
+ // Create document symbol params
+ uri := fmt.Sprintf("file://%s", filePath)
+ symbolParams := protocol.DocumentSymbolParams{
+ TextDocument: protocol.TextDocumentIdentifier{
+ URI: protocol.DocumentUri(uri),
+ },
+ }
+
+ // Get document symbols
+ symbolResult, err := client.DocumentSymbol(ctx, symbolParams)
+ if err != nil {
+ results = append(results, fmt.Sprintf("Error from %s: %s", lspName, err))
+ continue
+ }
+
+ // Process the symbol result
+ symbols := processDocumentSymbolResult(symbolResult)
+ if len(symbols) == 0 {
+ results = append(results, fmt.Sprintf("No symbols found by %s", lspName))
+ continue
+ }
+
+ // Format the symbols
+ results = append(results, fmt.Sprintf("Symbols found by %s:", lspName))
+ for _, symbol := range symbols {
+ results = append(results, formatSymbol(symbol, 1))
+ }
+ }
+
+ if len(results) == 0 {
+ return "No symbols found in the specified file."
+ }
+
+ return strings.Join(results, "\n")
+}
+
+func processDocumentSymbolResult(result protocol.Or_Result_textDocument_documentSymbol) []SymbolInfo {
+ var symbols []SymbolInfo
+
+ switch v := result.Value.(type) {
+ case []protocol.SymbolInformation:
+ for _, si := range v {
+ symbols = append(symbols, SymbolInfo{
+ Name: si.Name,
+ Kind: symbolKindToString(si.Kind),
+ Location: locationToString(si.Location),
+ Children: nil,
+ })
+ }
+ case []protocol.DocumentSymbol:
+ for _, ds := range v {
+ symbols = append(symbols, documentSymbolToSymbolInfo(ds))
+ }
+ }
+
+ return symbols
+}
+
+// SymbolInfo represents a symbol in a document
+type SymbolInfo struct {
+ Name string
+ Kind string
+ Location string
+ Children []SymbolInfo
+}
+
+func documentSymbolToSymbolInfo(symbol protocol.DocumentSymbol) SymbolInfo {
+ info := SymbolInfo{
+ Name: symbol.Name,
+ Kind: symbolKindToString(symbol.Kind),
+ Location: fmt.Sprintf("Line %d-%d",
+ symbol.Range.Start.Line+1,
+ symbol.Range.End.Line+1),
+ Children: []SymbolInfo{},
+ }
+
+ for _, child := range symbol.Children {
+ info.Children = append(info.Children, documentSymbolToSymbolInfo(child))
+ }
+
+ return info
+}
+
+func locationToString(location protocol.Location) string {
+ return fmt.Sprintf("Line %d-%d",
+ location.Range.Start.Line+1,
+ location.Range.End.Line+1)
+}
+
+func symbolKindToString(kind protocol.SymbolKind) string {
+ if kindStr, ok := protocol.TableKindMap[kind]; ok {
+ return kindStr
+ }
+ return "Unknown"
+}
+
+func formatSymbol(symbol SymbolInfo, level int) string {
+ indent := strings.Repeat(" ", level)
+ result := fmt.Sprintf("%s- %s (%s) %s", indent, symbol.Name, symbol.Kind, symbol.Location)
+
+ var childResults []string
+ for _, child := range symbol.Children {
+ childResults = append(childResults, formatSymbol(child, level+1))
+ }
+
+ if len(childResults) > 0 {
+ return result + "\n" + strings.Join(childResults, "\n")
+ }
+
+ return result
+} \ No newline at end of file
diff --git a/internal/llm/tools/references.go b/internal/llm/tools/lsp_references.go
index 6e0090f4e..6e0090f4e 100644
--- a/internal/llm/tools/references.go
+++ b/internal/llm/tools/lsp_references.go
diff --git a/internal/llm/tools/lsp_workspace_symbols.go b/internal/llm/tools/lsp_workspace_symbols.go
new file mode 100644
index 000000000..24ca577ea
--- /dev/null
+++ b/internal/llm/tools/lsp_workspace_symbols.go
@@ -0,0 +1,162 @@
+package tools
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "github.com/sst/opencode/internal/lsp"
+ "github.com/sst/opencode/internal/lsp/protocol"
+)
+
+type WorkspaceSymbolsParams struct {
+ Query string `json:"query"`
+}
+
+type workspaceSymbolsTool struct {
+ lspClients map[string]*lsp.Client
+}
+
+const (
+ WorkspaceSymbolsToolName = "workspaceSymbols"
+ workspaceSymbolsDescription = `Find symbols across the workspace matching a query.
+WHEN TO USE THIS TOOL:
+- Use when you need to find symbols across multiple files
+- Helpful for locating classes, functions, or variables in a project
+- Great for exploring large codebases
+
+HOW TO USE:
+- Provide a query string to search for symbols
+- Results show matching symbols from across the workspace
+
+FEATURES:
+- Searches across all files in the workspace
+- Shows symbol types (function, class, variable, etc.)
+- Provides location information for each symbol
+- Works with partial matches and fuzzy search (depending on LSP server)
+
+LIMITATIONS:
+- Requires a functioning LSP server for the file types
+- Results depend on the accuracy of the LSP server
+- Query capabilities vary by language server
+- May not work for all file types
+
+TIPS:
+- Use specific queries to narrow down results
+- Combine with DocSymbols tool for detailed file exploration
+- Use with Definition tool to jump to symbol definitions
+`
+)
+
+func NewWorkspaceSymbolsTool(lspClients map[string]*lsp.Client) BaseTool {
+ return &workspaceSymbolsTool{
+ lspClients,
+ }
+}
+
+func (b *workspaceSymbolsTool) Info() ToolInfo {
+ return ToolInfo{
+ Name: WorkspaceSymbolsToolName,
+ Description: workspaceSymbolsDescription,
+ Parameters: map[string]any{
+ "query": map[string]any{
+ "type": "string",
+ "description": "The query string to search for symbols",
+ },
+ },
+ Required: []string{"query"},
+ }
+}
+
+func (b *workspaceSymbolsTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) {
+ var params WorkspaceSymbolsParams
+ if err := json.Unmarshal([]byte(call.Input), &params); err != nil {
+ return NewTextErrorResponse(fmt.Sprintf("error parsing parameters: %s", err)), nil
+ }
+
+ lsps := b.lspClients
+
+ if len(lsps) == 0 {
+ return NewTextResponse("\nLSP clients are still initializing. Workspace symbols lookup will be available once they're ready.\n"), nil
+ }
+
+ output := getWorkspaceSymbols(ctx, params.Query, lsps)
+
+ return NewTextResponse(output), nil
+}
+
+func getWorkspaceSymbols(ctx context.Context, query string, lsps map[string]*lsp.Client) string {
+ var results []string
+
+ for lspName, client := range lsps {
+ // Create workspace symbol params
+ symbolParams := protocol.WorkspaceSymbolParams{
+ Query: query,
+ }
+
+ // Get workspace symbols
+ symbolResult, err := client.Symbol(ctx, symbolParams)
+ if err != nil {
+ results = append(results, fmt.Sprintf("Error from %s: %s", lspName, err))
+ continue
+ }
+
+ // Process the symbol result
+ symbols := processWorkspaceSymbolResult(symbolResult)
+ if len(symbols) == 0 {
+ results = append(results, fmt.Sprintf("No symbols found by %s for query '%s'", lspName, query))
+ continue
+ }
+
+ // Format the symbols
+ results = append(results, fmt.Sprintf("Symbols found by %s for query '%s':", lspName, query))
+ for _, symbol := range symbols {
+ results = append(results, fmt.Sprintf(" %s (%s) - %s", symbol.Name, symbol.Kind, symbol.Location))
+ }
+ }
+
+ if len(results) == 0 {
+ return fmt.Sprintf("No symbols found matching query '%s'.", query)
+ }
+
+ return strings.Join(results, "\n")
+}
+
+func processWorkspaceSymbolResult(result protocol.Or_Result_workspace_symbol) []SymbolInfo {
+ var symbols []SymbolInfo
+
+ switch v := result.Value.(type) {
+ case []protocol.SymbolInformation:
+ for _, si := range v {
+ symbols = append(symbols, SymbolInfo{
+ Name: si.Name,
+ Kind: symbolKindToString(si.Kind),
+ Location: formatWorkspaceLocation(si.Location),
+ Children: nil,
+ })
+ }
+ case []protocol.WorkspaceSymbol:
+ for _, ws := range v {
+ location := "Unknown location"
+ if ws.Location.Value != nil {
+ if loc, ok := ws.Location.Value.(protocol.Location); ok {
+ location = formatWorkspaceLocation(loc)
+ }
+ }
+ symbols = append(symbols, SymbolInfo{
+ Name: ws.Name,
+ Kind: symbolKindToString(ws.Kind),
+ Location: location,
+ Children: nil,
+ })
+ }
+ }
+
+ return symbols
+}
+
+func formatWorkspaceLocation(location protocol.Location) string {
+ path := strings.TrimPrefix(string(location.URI), "file://")
+ return fmt.Sprintf("%s:%d:%d", path, location.Range.Start.Line+1, location.Range.Start.Character+1)
+} \ No newline at end of file