-
Notifications
You must be signed in to change notification settings - Fork 0
Enrich truncated OpenRouter descriptions #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,8 +4,11 @@ import ( | |||||||||||||||||||||||||||||||||||||||||||
| "context" | ||||||||||||||||||||||||||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||||||||
| "html" | ||||||||||||||||||||||||||||||||||||||||||||
| "io" | ||||||||||||||||||||||||||||||||||||||||||||
| "net/http" | ||||||||||||||||||||||||||||||||||||||||||||
| "net/url" | ||||||||||||||||||||||||||||||||||||||||||||
| "regexp" | ||||||||||||||||||||||||||||||||||||||||||||
| "strconv" | ||||||||||||||||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||||||||||||||||
| "time" | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -62,7 +65,11 @@ type ConfigurableProvider interface { | |||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // --- OpenRouter /models fetcher --- | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| var openRouterHTTPClient = &http.Client{Timeout: 20 * time.Second} | ||||||||||||||||||||||||||||||||||||||||||||
| var ( | ||||||||||||||||||||||||||||||||||||||||||||
| openRouterHTTPClient = &http.Client{Timeout: 20 * time.Second} | ||||||||||||||||||||||||||||||||||||||||||||
| openRouterPageBaseURL = "https://openrouter.ai" | ||||||||||||||||||||||||||||||||||||||||||||
| openRouterDescriptionPayload = regexp.MustCompile(`\\"description\\":\\"((?:\\\\.|[^\\"])*)\\"`) | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // FetchOpenRouterModels pulls the full catalog from OpenRouter and returns a | ||||||||||||||||||||||||||||||||||||||||||||
| // map keyed by model id. Prices are normalised to USD per 1M tokens (OpenRouter | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -133,6 +140,85 @@ func parseOpenRouterModels(body []byte) (map[string]Capabilities, error) { | |||||||||||||||||||||||||||||||||||||||||||
| return out, nil | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // OpenRouterDescriptionLooksTruncated reports descriptions that already arrive | ||||||||||||||||||||||||||||||||||||||||||||
| // from OpenRouter with an ellipsis. Those are upstream-shortened strings, not a | ||||||||||||||||||||||||||||||||||||||||||||
| // UI clamp, so callers may optionally enrich them from the model page payload. | ||||||||||||||||||||||||||||||||||||||||||||
| func OpenRouterDescriptionLooksTruncated(desc string) bool { | ||||||||||||||||||||||||||||||||||||||||||||
| desc = strings.TrimSpace(desc) | ||||||||||||||||||||||||||||||||||||||||||||
| return strings.HasSuffix(desc, "...") || strings.HasSuffix(desc, "…") | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // FetchOpenRouterModelDescription fetches a model page and extracts the longest | ||||||||||||||||||||||||||||||||||||||||||||
| // matching description from its SSR payload. OpenRouter's public /models API can | ||||||||||||||||||||||||||||||||||||||||||||
| // return shortened descriptions ending in "...", while the model page payload | ||||||||||||||||||||||||||||||||||||||||||||
| // usually carries the full text. | ||||||||||||||||||||||||||||||||||||||||||||
| func FetchOpenRouterModelDescription(ctx context.Context, modelID, currentDescription string) (string, error) { | ||||||||||||||||||||||||||||||||||||||||||||
| req, err := http.NewRequestWithContext(ctx, http.MethodGet, openRouterModelPageURL(modelID), nil) | ||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||
| return "", err | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| resp, err := openRouterHTTPClient.Do(req) | ||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||
| return "", fmt.Errorf("openrouter model page: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| defer resp.Body.Close() | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| body, err := io.ReadAll(io.LimitReader(resp.Body, 4*1024*1024)) | ||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||
| return "", fmt.Errorf("openrouter model page: read: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+160
to
+168
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Read the file around the specified lines
cat -n internal/llm/capabilities.go | sed -n '155,175p'Repository: Dzarlax-AI/personal_assistant Length of output: 1061 🏁 Script executed: # Check for other similar patterns in the file to understand error handling patterns
rg -n "defer.*Close\(\)" internal/llm/capabilities.go -A 5 | head -40Repository: Dzarlax-AI/personal_assistant Length of output: 467 🏁 Script executed: # Search for proper error handling of resp.Body.Close() in the codebase
rg -n "closeErr\s*:=\s*resp\.Body\.Close\(\)" --type go -A 2Repository: Dzarlax-AI/personal_assistant Length of output: 55 🏁 Script executed: # Search for patterns where Close() errors are explicitly handled
rg "\.Close\(\)" --type go -A 2 | grep -E "(closeErr|errClose|err.*Close)" | head -20Repository: Dzarlax-AI/personal_assistant Length of output: 55 🏁 Script executed: # Look for other HTTP response body reading patterns in the codebase
rg "io\.ReadAll.*resp\.Body" --type go -B 2 -A 3 | head -60Repository: Dzarlax-AI/personal_assistant Length of output: 3010 🏁 Script executed: # Check if there's any linter configuration for errcheck in the repo
find . -name ".golangci.yml" -o -name ".golangci.yaml" -o -name "golangci.yml" | head -5Repository: Dzarlax-AI/personal_assistant Length of output: 55 🏁 Script executed: # Look for linting configuration
fd -e "golangci*" -o -e "*.lint*"Repository: Dzarlax-AI/personal_assistant Length of output: 190 🏁 Script executed: # Look for linting configuration files
find . -maxdepth 2 -type f \( -name "*.golangci*" -o -name ".lint*" \) 2>/dev/nullRepository: Dzarlax-AI/personal_assistant Length of output: 55 🏁 Script executed: # Check the exact proposed fix in the review - verify the error message issue
# Let me see if there are any other close error patterns in the repo
rg "resp\.Body" --type go -A 5 | grep -E "(Close|err)" | head -30Repository: Dzarlax-AI/personal_assistant Length of output: 2158 🏁 Script executed: # Check if there's any pattern of handling close errors elsewhere in Go files
rg "defer.*Close" --type go -B 2 -A 8 | grep -E "(defer|closeErr|Close|\.Close)" | head -40Repository: Dzarlax-AI/personal_assistant Length of output: 2000 Handle Line 164 currently ignores close errors via defer, which can mask transport/resource issues. The proposed fix has a variable name mismatch—the error message still references Proposed fix- defer resp.Body.Close()
-
- body, err := io.ReadAll(io.LimitReader(resp.Body, 4*1024*1024))
- if err != nil {
- return "", fmt.Errorf("openrouter model page: read: %w", err)
+ body, readErr := io.ReadAll(io.LimitReader(resp.Body, 4*1024*1024))
+ closeErr := resp.Body.Close()
+ if readErr != nil {
+ return "", fmt.Errorf("openrouter model page: read: %w", readErr)
+ }
+ if closeErr != nil {
+ return "", fmt.Errorf("openrouter model page: close: %w", closeErr)
}📝 Committable suggestion
Suggested change
🧰 Tools🪛 golangci-lint (2.12.2)[error] 164-164: Error return value of (errcheck) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| if resp.StatusCode != http.StatusOK { | ||||||||||||||||||||||||||||||||||||||||||||
| return "", fmt.Errorf("openrouter model page: HTTP %d: %s", resp.StatusCode, string(body)) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| full := extractOpenRouterPageDescription(body, currentDescription) | ||||||||||||||||||||||||||||||||||||||||||||
| if full == "" { | ||||||||||||||||||||||||||||||||||||||||||||
| return "", fmt.Errorf("openrouter model page: full description not found") | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| return full, nil | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| func openRouterModelPageURL(modelID string) string { | ||||||||||||||||||||||||||||||||||||||||||||
| parts := strings.Split(modelID, "/") | ||||||||||||||||||||||||||||||||||||||||||||
| for i := range parts { | ||||||||||||||||||||||||||||||||||||||||||||
| parts[i] = url.PathEscape(parts[i]) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| return strings.TrimRight(openRouterPageBaseURL, "/") + "/" + strings.Join(parts, "/") | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| func extractOpenRouterPageDescription(body []byte, currentDescription string) string { | ||||||||||||||||||||||||||||||||||||||||||||
| current := normalizeDescriptionText(currentDescription) | ||||||||||||||||||||||||||||||||||||||||||||
| prefix := strings.TrimSpace(strings.TrimSuffix(strings.TrimSuffix(current, "..."), "…")) | ||||||||||||||||||||||||||||||||||||||||||||
| if len(prefix) > 120 { | ||||||||||||||||||||||||||||||||||||||||||||
| prefix = prefix[:120] | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+191
to
+193
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cd /dev/null || true
# Search for the file and examine the context
fd -t f capabilities.go internal/llmRepository: Dzarlax-AI/personal_assistant Length of output: 170 🏁 Script executed: # Read the relevant section around lines 191-193
cat -n internal/llm/capabilities.go | sed -n '170,210p'Repository: Dzarlax-AI/personal_assistant Length of output: 1706 🏁 Script executed: # Get more context to understand what prefix is and how it's used
cat -n internal/llm/capabilities.go | sed -n '145,220p'Repository: Dzarlax-AI/personal_assistant Length of output: 3206 Avoid byte-slicing UTF-8 when capping the prefix. At line 192, Proposed fix if len(prefix) > 120 {
- prefix = prefix[:120]
+ r := []rune(prefix)
+ prefix = string(r[:120])
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
| var best string | ||||||||||||||||||||||||||||||||||||||||||||
| for _, m := range openRouterDescriptionPayload.FindAllSubmatch(body, -1) { | ||||||||||||||||||||||||||||||||||||||||||||
| if len(m) != 2 { | ||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| raw := string(m[1]) | ||||||||||||||||||||||||||||||||||||||||||||
| decoded, err := strconv.Unquote(`"` + raw + `"`) | ||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| decoded = normalizeDescriptionText(html.UnescapeString(decoded)) | ||||||||||||||||||||||||||||||||||||||||||||
| if decoded == "" || len(decoded) <= len(current) || OpenRouterDescriptionLooksTruncated(decoded) { | ||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| if prefix != "" && !strings.HasPrefix(decoded, prefix) { | ||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| if len(decoded) > len(best) { | ||||||||||||||||||||||||||||||||||||||||||||
| best = decoded | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| return best | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| func normalizeDescriptionText(s string) string { | ||||||||||||||||||||||||||||||||||||||||||||
| return strings.Join(strings.Fields(strings.TrimSpace(s)), " ") | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| func containsStr(s []string, target string) bool { | ||||||||||||||||||||||||||||||||||||||||||||
| for _, v := range s { | ||||||||||||||||||||||||||||||||||||||||||||
| if strings.EqualFold(v, target) { | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prevent cache row clobbering when capability lookup misses.
At Line 672,
lookupCapsForcan return a zero-value struct on miss/error; then Line 677 upserts that struct, which can overwrite persisted capability fields (pricing, flags, context length) with zeros.Proposed fix
🤖 Prompt for AI Agents