| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- package agentserver
- import (
- "net/url"
- "strings"
- )
- // OwnerKind 代理请求的归属校验方式
- type OwnerKind int
- const (
- // OwnerNone 只读元数据,无需归属校验(/v1/profiles、/v1/providers)
- OwnerNone OwnerKind = iota
- // OwnerSession 路径中的会话 ID
- OwnerSession
- // OwnerQuerySession 查询参数 sessionId
- OwnerQuerySession
- // OwnerRun 路径中的 runId
- OwnerRun
- // OwnerInteraction 路径中的 interactionId
- OwnerInteraction
- // OwnerFile 路径中的 fileId
- OwnerFile
- )
- // Action 需要特殊处理的接口
- type Action int
- const (
- ActionPassthrough Action = iota
- ActionStream // GET /sessions/:id/stream(SSE 透传 + 解析事件)
- ActionEvents // GET /sessions/:id/events(解析事件)
- ActionSessionDetail // GET /sessions/:id(记录 activeRunId、待处理交互)
- ActionSessionPatch // PATCH /sessions/:id(只保留 title、model)
- ActionSendMessage // POST /sessions/:id/messages(记录 runId、刷新 lastActive)
- ActionUploadFiles // POST /sessions/:id/files(记录 fileId)
- ActionListFiles // GET /sessions/:id/files(记录 fileId)
- ActionInteractions // GET /interactions?sessionId=(记录 interactionId)
- )
- // Route 白名单匹配结果
- type Route struct {
- Owner OwnerKind
- Id string // 用于归属校验的 ID(会话 / run / 交互 / 文件)
- Action Action
- }
- // MatchRoute 按白名单(technical-design §3.5)匹配代理请求。
- // path 为 /v1 之后的部分(如 /sessions/ses_x/stream);不在白名单内返回 false。
- func MatchRoute(method, path string, query url.Values) (Route, bool) {
- seg := splitPath(path)
- n := len(seg)
- switch {
- case method == "GET" && n == 1 && (seg[0] == "profiles" || seg[0] == "providers"):
- return Route{Owner: OwnerNone}, true
- case n >= 2 && seg[0] == "sessions" && validId(seg[1], PrefixSession):
- id := seg[1]
- if n == 2 {
- switch method {
- case "GET":
- return Route{Owner: OwnerSession, Id: id, Action: ActionSessionDetail}, true
- case "PATCH":
- return Route{Owner: OwnerSession, Id: id, Action: ActionSessionPatch}, true
- }
- return Route{}, false
- }
- if n != 3 {
- return Route{}, false
- }
- switch {
- case method == "GET" && seg[2] == "events":
- return Route{Owner: OwnerSession, Id: id, Action: ActionEvents}, true
- case method == "GET" && seg[2] == "stream":
- return Route{Owner: OwnerSession, Id: id, Action: ActionStream}, true
- case method == "GET" && seg[2] == "files":
- return Route{Owner: OwnerSession, Id: id, Action: ActionListFiles}, true
- case method == "POST" && seg[2] == "files":
- return Route{Owner: OwnerSession, Id: id, Action: ActionUploadFiles}, true
- case method == "POST" && seg[2] == "messages":
- return Route{Owner: OwnerSession, Id: id, Action: ActionSendMessage}, true
- }
- return Route{}, false
- case n >= 2 && seg[0] == "runs" && validId(seg[1], PrefixRun):
- if (method == "GET" && n == 2) || (method == "POST" && n == 3 && seg[2] == "interrupt") {
- return Route{Owner: OwnerRun, Id: seg[1]}, true
- }
- return Route{}, false
- case method == "GET" && n == 1 && seg[0] == "interactions":
- sid := query.Get("sessionId")
- if !validId(sid, PrefixSession) {
- return Route{}, false
- }
- return Route{Owner: OwnerQuerySession, Id: sid, Action: ActionInteractions}, true
- case method == "POST" && n == 2 && seg[0] == "interactions" && validId(seg[1], PrefixInteraction):
- return Route{Owner: OwnerInteraction, Id: seg[1]}, true
- case method == "GET" && n == 2 && seg[0] == "files" && validId(seg[1], PrefixFile):
- return Route{Owner: OwnerFile, Id: seg[1]}, true
- }
- return Route{}, false
- }
- func splitPath(p string) []string {
- p = strings.Trim(p, "/")
- if p == "" {
- return nil
- }
- return strings.Split(p, "/")
- }
- // validId 校验带前缀的 ID,只允许字母、数字、下划线和连字符,防止路径穿越
- func validId(id, prefix string) bool {
- if !strings.HasPrefix(id, prefix) || len(id) <= len(prefix) || len(id) > 64 {
- return false
- }
- for _, r := range id {
- if !(r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || r == '_' || r == '-') {
- return false
- }
- }
- return true
- }
- // ResourceKindOf 按前缀判断子资源类型
- func ResourceKindOf(id string) OwnerKind {
- switch {
- case strings.HasPrefix(id, PrefixRun):
- return OwnerRun
- case strings.HasPrefix(id, PrefixInteraction):
- return OwnerInteraction
- case strings.HasPrefix(id, PrefixFile):
- return OwnerFile
- }
- return OwnerNone
- }
|