Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

72 lines
2.6 KiB
Go

package daemon
import "context"
// RouteInputs is what a transport extracts from its inbound tools/call
// frame for routing: the tool name, the {"arguments":...} body
// RouteToolCall forwards verbatim, the session cwd, and an explicit
// workspace scope override (either may be empty).
type RouteInputs struct {
ToolName string
Body []byte
Cwd string
Scope string
}
// Outcome tells the caller how to frame a routed tool call. When Proxied
// is true the route resolved to a remote (or a gate refusal) and
// Out/Status carry the response bytes to frame for the transport; when
// Proxied is false the caller runs its own in-process local dispatch.
type Outcome struct {
Proxied bool
Out []byte
Status int
Err error // routing error (ErrRouteUnresolved etc.); caller may log
}
// ProxyDecision is the single peek→route→outcome helper shared by all
// three transports (the daemon AF_UNIX dispatcher, the /v1 HTTP handler,
// and the Streamable-HTTP transport). It owns the routing decision —
// computing the per-call effective enabled-set once and invoking
// RouteToolCall — while transport-specific frame parsing and RESPONSE
// FRAMING stay at each call site. The router is read through a dynamic
// accessor so a live ControlProxy swap is reflected.
type ProxyDecision struct {
router func() *Router
}
// NewProxyDecision builds a decision helper over a dynamic router
// accessor (typically an atomic.Pointer[Router].Load).
func NewProxyDecision(router func() *Router) *ProxyDecision {
return &ProxyDecision{router: router}
}
// Decide computes the effective enabled-set once (from the published
// roster overlaid with the session's overrides), threads it through
// RouteContext, runs RouteToolCall, and classifies the result. A nil
// router or an unresolved/local route yields Proxied=false (the caller
// dispatches locally); any resolved remote response or gate refusal
// yields Proxied=true with the bytes + status to frame.
func (d *ProxyDecision) Decide(ctx context.Context, in RouteInputs, sess *Session) Outcome {
r := d.router()
if r == nil {
return Outcome{}
}
sid := ""
if sess != nil {
sid = sess.ID
}
out, status, err := r.RouteToolCall(ctx, in.ToolName, in.Body, RouteContext{
Cwd: in.Cwd,
ScopeOverride: in.Scope,
SessionID: sid,
EnabledRemotes: r.EffectiveEnabledRemotes(sess),
})
if err != nil || status == 0 {
// ErrRouteUnresolved or a routing failure — fall through to the
// caller's local dispatch path (the same body works there).
return Outcome{Err: err}
}
return Outcome{Proxied: true, Out: out, Status: status}
}