summaryrefslogtreecommitdiffhomepage
path: root/internal/llm
diff options
context:
space:
mode:
authorKujtim Hoxha <[email protected]>2025-04-19 15:15:29 +0200
committerKujtim Hoxha <[email protected]>2025-04-21 13:42:29 +0200
commit2b5a33e476ae3c6b5c6345777d20792786836dda (patch)
treeca22c0a7d6c8f73a21e2b67f31b927b7df187d4d /internal/llm
parentbf8cd3bd128d97cf93fcd1481c37db0e46945fd2 (diff)
downloadopencode-2b5a33e476ae3c6b5c6345777d20792786836dda.tar.gz
opencode-2b5a33e476ae3c6b5c6345777d20792786836dda.zip
lsp improvements
Diffstat (limited to 'internal/llm')
-rw-r--r--internal/llm/models/gemini.go63
-rw-r--r--internal/llm/models/models.go5
-rw-r--r--internal/llm/prompt/coder.go2
-rw-r--r--internal/llm/provider/gemini.go1
-rw-r--r--internal/llm/tools/grep.go91
5 files changed, 124 insertions, 38 deletions
diff --git a/internal/llm/models/gemini.go b/internal/llm/models/gemini.go
new file mode 100644
index 000000000..00bf7387f
--- /dev/null
+++ b/internal/llm/models/gemini.go
@@ -0,0 +1,63 @@
+package models
+
+const (
+ ProviderGemini ModelProvider = "gemini"
+
+ // Models
+ Gemini25Flash ModelID = "gemini-2.5-flash"
+ Gemini25 ModelID = "gemini-2.5"
+ Gemini20Flash ModelID = "gemini-2.0-flash"
+ Gemini20FlashLite ModelID = "gemini-2.0-flash-lite"
+)
+
+var GeminiModels = map[ModelID]Model{
+ Gemini25Flash: {
+ ID: Gemini25Flash,
+ Name: "Gemini 2.5 Flash",
+ Provider: ProviderGemini,
+ APIModel: "gemini-2.5-flash-preview-04-17",
+ CostPer1MIn: 0.15,
+ CostPer1MInCached: 0,
+ CostPer1MOutCached: 0,
+ CostPer1MOut: 0.60,
+ ContextWindow: 1000000,
+ DefaultMaxTokens: 50000,
+ },
+ Gemini25: {
+ ID: Gemini25,
+ Name: "Gemini 2.5 Pro",
+ Provider: ProviderGemini,
+ APIModel: "gemini-2.5-pro-preview-03-25",
+ CostPer1MIn: 1.25,
+ CostPer1MInCached: 0,
+ CostPer1MOutCached: 0,
+ CostPer1MOut: 10,
+ ContextWindow: 1000000,
+ DefaultMaxTokens: 50000,
+ },
+
+ Gemini20Flash: {
+ ID: Gemini20Flash,
+ Name: "Gemini 2.0 Flash",
+ Provider: ProviderGemini,
+ APIModel: "gemini-2.0-flash",
+ CostPer1MIn: 0.10,
+ CostPer1MInCached: 0,
+ CostPer1MOutCached: 0,
+ CostPer1MOut: 0.40,
+ ContextWindow: 1000000,
+ DefaultMaxTokens: 6000,
+ },
+ Gemini20FlashLite: {
+ ID: Gemini20FlashLite,
+ Name: "Gemini 2.0 Flash Lite",
+ Provider: ProviderGemini,
+ APIModel: "gemini-2.0-flash-lite",
+ CostPer1MIn: 0.05,
+ CostPer1MInCached: 0,
+ CostPer1MOutCached: 0,
+ CostPer1MOut: 0.30,
+ ContextWindow: 1000000,
+ DefaultMaxTokens: 6000,
+ },
+}
diff --git a/internal/llm/models/models.go b/internal/llm/models/models.go
index aba4a10c3..cccbd2765 100644
--- a/internal/llm/models/models.go
+++ b/internal/llm/models/models.go
@@ -23,9 +23,6 @@ type Model struct {
// Model IDs
const ( // GEMINI
- GEMINI25 ModelID = "gemini-2.5"
- GRMINI20Flash ModelID = "gemini-2.0-flash"
-
// GROQ
QWENQwq ModelID = "qwen-qwq"
@@ -35,7 +32,6 @@ const ( // GEMINI
const (
ProviderBedrock ModelProvider = "bedrock"
- ProviderGemini ModelProvider = "gemini"
ProviderGROQ ModelProvider = "groq"
// ForTests
@@ -95,4 +91,5 @@ var SupportedModels = map[ModelID]Model{
func init() {
maps.Copy(SupportedModels, AnthropicModels)
maps.Copy(SupportedModels, OpenAIModels)
+ maps.Copy(SupportedModels, GeminiModels)
}
diff --git a/internal/llm/prompt/coder.go b/internal/llm/prompt/coder.go
index d7ca7b2fd..cc0da0313 100644
--- a/internal/llm/prompt/coder.go
+++ b/internal/llm/prompt/coder.go
@@ -68,6 +68,7 @@ You MUST adhere to the following criteria when executing the task:
- Do NOT show the full contents of large files you have already written, unless the user explicitly asks for them.
- When doing things with paths, always use use the full path, if the working directory is /abc/xyz and you want to edit the file abc.go in the working dir refer to it as /abc/xyz/abc.go.
- If you send a path not including the working dir, the working dir will be prepended to it.
+- Remember the user does not see the full output of tools
`
const baseAnthropicCoderPrompt = `You are OpenCode, an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
@@ -162,6 +163,7 @@ NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTAN
# Tool usage policy
- When doing file search, prefer to use the Agent tool in order to reduce context usage.
- If you intend to call multiple tools and there are no dependencies between the calls, make all of the independent calls in the same function_calls block.
+- IMPORTANT: The user does not see the full output of the tool responses, so if you need the output of the tool for the response make sure to summarize it for the user.
You MUST answer concisely with fewer than 4 lines of text (not including tool use or code generation), unless user asks for detail.`
diff --git a/internal/llm/provider/gemini.go b/internal/llm/provider/gemini.go
index 384bff900..a5e6ed877 100644
--- a/internal/llm/provider/gemini.go
+++ b/internal/llm/provider/gemini.go
@@ -567,4 +567,3 @@ func contains(s string, substrs ...string) bool {
}
return false
}
-
diff --git a/internal/llm/tools/grep.go b/internal/llm/tools/grep.go
index 086a5e686..475370ffb 100644
--- a/internal/llm/tools/grep.go
+++ b/internal/llm/tools/grep.go
@@ -10,6 +10,7 @@ import (
"path/filepath"
"regexp"
"sort"
+ "strconv"
"strings"
"time"
@@ -24,8 +25,10 @@ type GrepParams struct {
}
type grepMatch struct {
- path string
- modTime time.Time
+ path string
+ modTime time.Time
+ lineNum int
+ lineText string
}
type GrepResponseMetadata struct {
@@ -147,13 +150,26 @@ func (g *grepTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
if len(matches) == 0 {
output = "No files found"
} else {
- output = fmt.Sprintf("Found %d file%s\n%s",
- len(matches),
- pluralize(len(matches)),
- strings.Join(matches, "\n"))
+ output = fmt.Sprintf("Found %d matches\n", len(matches))
+
+ currentFile := ""
+ for _, match := range matches {
+ if currentFile != match.path {
+ if currentFile != "" {
+ output += "\n"
+ }
+ currentFile = match.path
+ output += fmt.Sprintf("%s:\n", match.path)
+ }
+ if match.lineNum > 0 {
+ output += fmt.Sprintf(" Line %d: %s\n", match.lineNum, match.lineText)
+ } else {
+ output += fmt.Sprintf(" %s\n", match.path)
+ }
+ }
if truncated {
- output += "\n\n(Results are truncated. Consider using a more specific path or pattern.)"
+ output += "\n(Results are truncated. Consider using a more specific path or pattern.)"
}
}
@@ -166,14 +182,7 @@ func (g *grepTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
), nil
}
-func pluralize(count int) string {
- if count == 1 {
- return ""
- }
- return "s"
-}
-
-func searchFiles(pattern, rootPath, include string, limit int) ([]string, bool, error) {
+func searchFiles(pattern, rootPath, include string, limit int) ([]grepMatch, bool, error) {
matches, err := searchWithRipgrep(pattern, rootPath, include)
if err != nil {
matches, err = searchFilesWithRegex(pattern, rootPath, include)
@@ -191,12 +200,7 @@ func searchFiles(pattern, rootPath, include string, limit int) ([]string, bool,
matches = matches[:limit]
}
- results := make([]string, len(matches))
- for i, m := range matches {
- results[i] = m.path
- }
-
- return results, truncated, nil
+ return matches, truncated, nil
}
func searchWithRipgrep(pattern, path, include string) ([]grepMatch, error) {
@@ -205,7 +209,8 @@ func searchWithRipgrep(pattern, path, include string) ([]grepMatch, error) {
return nil, fmt.Errorf("ripgrep not found: %w", err)
}
- args := []string{"-l", pattern}
+ // Use -n to show line numbers and include the matched line
+ args := []string{"-n", pattern}
if include != "" {
args = append(args, "--glob", include)
}
@@ -228,14 +233,29 @@ func searchWithRipgrep(pattern, path, include string) ([]grepMatch, error) {
continue
}
- fileInfo, err := os.Stat(line)
+ // Parse ripgrep output format: file:line:content
+ parts := strings.SplitN(line, ":", 3)
+ if len(parts) < 3 {
+ continue
+ }
+
+ filePath := parts[0]
+ lineNum, err := strconv.Atoi(parts[1])
+ if err != nil {
+ continue
+ }
+ lineText := parts[2]
+
+ fileInfo, err := os.Stat(filePath)
if err != nil {
continue // Skip files we can't access
}
matches = append(matches, grepMatch{
- path: line,
- modTime: fileInfo.ModTime(),
+ path: filePath,
+ modTime: fileInfo.ModTime(),
+ lineNum: lineNum,
+ lineText: lineText,
})
}
@@ -276,15 +296,17 @@ func searchFilesWithRegex(pattern, rootPath, include string) ([]grepMatch, error
return nil
}
- match, err := fileContainsPattern(path, regex)
+ match, lineNum, lineText, err := fileContainsPattern(path, regex)
if err != nil {
return nil // Skip files we can't read
}
if match {
matches = append(matches, grepMatch{
- path: path,
- modTime: info.ModTime(),
+ path: path,
+ modTime: info.ModTime(),
+ lineNum: lineNum,
+ lineText: lineText,
})
if len(matches) >= 200 {
@@ -301,21 +323,24 @@ func searchFilesWithRegex(pattern, rootPath, include string) ([]grepMatch, error
return matches, nil
}
-func fileContainsPattern(filePath string, pattern *regexp.Regexp) (bool, error) {
+func fileContainsPattern(filePath string, pattern *regexp.Regexp) (bool, int, string, error) {
file, err := os.Open(filePath)
if err != nil {
- return false, err
+ return false, 0, "", err
}
defer file.Close()
scanner := bufio.NewScanner(file)
+ lineNum := 0
for scanner.Scan() {
- if pattern.MatchString(scanner.Text()) {
- return true, nil
+ lineNum++
+ line := scanner.Text()
+ if pattern.MatchString(line) {
+ return true, lineNum, line, nil
}
}
- return false, scanner.Err()
+ return false, 0, "", scanner.Err()
}
func globToRegex(glob string) string {