You've already forked agentic-coding-workflow
87 lines
2.7 KiB
Go
87 lines
2.7 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io/fs"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/yan1h/agent-coding-workflow/internal/acp/pathsafe"
|
|
)
|
|
|
|
// FsReadHandler handles fs/read_text_file (spec §5.4).
|
|
type FsReadHandler struct{}
|
|
|
|
// NewFsReadHandler constructs an FsReadHandler.
|
|
func NewFsReadHandler() *FsReadHandler { return &FsReadHandler{} }
|
|
|
|
// fsReadParams is the ACP-protocol input for fs/read_text_file.
|
|
type fsReadParams struct {
|
|
SessionID string `json:"sessionId"`
|
|
Path string `json:"path"`
|
|
Line *int `json:"line,omitempty"`
|
|
Limit *int `json:"limit,omitempty"`
|
|
}
|
|
|
|
type fsReadResult struct {
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
// Handle reads a file inside sess.CwdPath, returning content (with optional
|
|
// line/limit slicing). Returns FsError for path-escape or read failure.
|
|
func (h *FsReadHandler) Handle(_ context.Context, sess SessionContext, paramsJSON json.RawMessage) FsResult {
|
|
var p fsReadParams
|
|
if err := json.Unmarshal(paramsJSON, &p); err != nil {
|
|
return FsResult{Err: &FsError{Code: -32602, Message: "invalid params: " + err.Error()}}
|
|
}
|
|
abs, err := resolveSafePath(sess.CwdPath, p.Path)
|
|
if err != nil {
|
|
return FsResult{Err: &FsError{Code: -32001, Message: err.Error()}}
|
|
}
|
|
data, err := os.ReadFile(abs)
|
|
if err != nil {
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
return FsResult{Err: &FsError{Code: -32603, Message: "file not found"}}
|
|
}
|
|
return FsResult{Err: &FsError{Code: -32603, Message: "read: " + err.Error()}}
|
|
}
|
|
content := string(data)
|
|
if p.Line != nil || p.Limit != nil {
|
|
content = sliceLines(content, p.Line, p.Limit)
|
|
}
|
|
out, _ := json.Marshal(fsReadResult{Content: content})
|
|
return FsResult{OK: out}
|
|
}
|
|
|
|
// sliceLines returns lines[line:line+limit] joined by "\n".
|
|
// line is 0-based; limit caps the line count.
|
|
func sliceLines(s string, line, limit *int) string {
|
|
lines := strings.Split(s, "\n")
|
|
start := 0
|
|
if line != nil && *line > 0 {
|
|
start = *line
|
|
}
|
|
if start > len(lines) {
|
|
start = len(lines)
|
|
}
|
|
end := len(lines)
|
|
if limit != nil && *limit >= 0 {
|
|
// overflow-safe:用剩余行数为上限做钳制,避免 start+*limit 在 *limit 很大
|
|
// (如 math.MaxInt)时整数溢出为负,绕过下界判断后导致 lines[start:end] panic。
|
|
remaining := len(lines) - start
|
|
if *limit < remaining {
|
|
end = start + *limit
|
|
}
|
|
}
|
|
return strings.Join(lines[start:end], "\n")
|
|
}
|
|
|
|
// resolveSafePath 委托共享实现 pathsafe.ResolveSafePath。
|
|
// handlers 子包不能 import internal/acp 主包(acp 依赖 handlers,会循环),
|
|
// 故校验逻辑抽到 internal/acp/pathsafe 子包共用。
|
|
func resolveSafePath(cwd, requested string) (string, error) {
|
|
return pathsafe.ResolveSafePath(cwd, requested)
|
|
}
|