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
72 lines
2.6 KiB
Go
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}
|
|
}
|