mirror of
https://github.com/lukaszraczylo/filepuff-mcp.git
synced 2026-06-05 22:23:50 +00:00
5ad975ee7a
* v2.0: token-optimization overhaul Additive (backward-compatible flags): - file_read: skeleton mode, strip (imports/license/block_comments), compact_line_numbers, 8-char etag with prefix-match compat - ast_query: format=verbose|compact|location, pagination cursor - file_search: cluster mode, pagination cursor - lsp_query (references): compact output Breaking (v2): - Preambles removed; opt-in verbose=true restores - edit_apply: response=count|diff|none, default count - ping tool removed - symbol_at/find_definition/find_references merged into lsp_query - Tool descriptions trimmed -83%, help moved to filepuff://help/<tool> - Batch file_read dedups by etag Protocol: - ResourceLink returned for file_read >64 KiB (force_inline override) - OnAfterInitialize hook reads capabilities.experimental.filepuff for session defaults (default_format, default_max_results, default_cluster, compact_refs, line_numbers, resource_link_threshold) * fix: drop --max-total-count from ripgrep args The flag does not exist in stable ripgrep (confirmed up to 15.1.0 -- "unrecognized flag --max-total-count, similar flags that are available: --max-count"). Every file_search call failed on hosts with stock rg. --max-count is per-file, not a drop-in replacement, so rely on the in-process truncation in parseOutput that was already the documented safety net.
66 lines
1.8 KiB
Go
66 lines
1.8 KiB
Go
// Package cursor implements opaque pagination cursors for MCP tools.
|
|
// A cursor encodes an offset into a result stream plus a query hash so stale
|
|
// cursors from different queries fail cleanly.
|
|
//
|
|
// Encoding: base64url(json({"offset":N,"query_hash":"hex"}))
|
|
// The query_hash is a hex-encoded sha256 over the deterministic query params.
|
|
package cursor
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
json "github.com/goccy/go-json"
|
|
)
|
|
|
|
// payload is the JSON structure inside a cursor.
|
|
type payload struct {
|
|
Offset int `json:"offset"`
|
|
QueryHash string `json:"query_hash"`
|
|
}
|
|
|
|
// Encode creates an opaque cursor string from an offset and query hash.
|
|
func Encode(offset int, queryHash string) string {
|
|
p := payload{Offset: offset, QueryHash: queryHash}
|
|
b, _ := json.Marshal(p)
|
|
return base64.RawURLEncoding.EncodeToString(b)
|
|
}
|
|
|
|
// Decode parses a cursor string. Returns offset, queryHash, error.
|
|
func Decode(cursor string) (int, string, error) {
|
|
b, err := base64.RawURLEncoding.DecodeString(cursor)
|
|
if err != nil {
|
|
return 0, "", fmt.Errorf("invalid cursor encoding: %w", err)
|
|
}
|
|
var p payload
|
|
if err := json.Unmarshal(b, &p); err != nil {
|
|
return 0, "", fmt.Errorf("invalid cursor payload: %w", err)
|
|
}
|
|
return p.Offset, p.QueryHash, nil
|
|
}
|
|
|
|
// HashParams computes a deterministic query hash from a set of key=value params.
|
|
// Keys are sorted before hashing so order doesn't matter.
|
|
func HashParams(params map[string]string) string {
|
|
keys := make([]string, 0, len(params))
|
|
for k := range params {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
|
|
var sb strings.Builder
|
|
for _, k := range keys {
|
|
sb.WriteString(k)
|
|
sb.WriteByte('=')
|
|
sb.WriteString(params[k])
|
|
sb.WriteByte('\n')
|
|
}
|
|
|
|
sum := sha256.Sum256([]byte(sb.String()))
|
|
return hex.EncodeToString(sum[:])
|
|
}
|