// Package caps implements every CoreServices capability interface // (core/plugin/deps.go) as a guest-side stub that marshals to the WO-WZ-001 // capability messages (abiv1) and dispatches them through a single generic // guest→host transport. Plugin code keeps compiling and calling against // content.Content, settings.Settings, plugin.PluginBridge, etc. — unchanged — // while the concrete work now happens host-side over the wasm ABI. // // # The transport seam // // A capability call is "marshal a family request, invoke // '.', unmarshal the reply". That transport is injected as a // CallFunc so the marshaling logic in this package stays natively testable // (a fake CallFunc in caps_roundtrip_test.go), while the wasm guest shim // (package wasmguest, wasip1) binds the real host_call-backed transport. This // package deliberately does NOT import wasmguest: wasmguest imports caps (to // assemble the services and reach the guest-side RAG fetcher registry), so the // dependency only points one way. // // # Method disposition // // Every CoreServices member is either a stub here or documented host-side in // core/docs/wasm-abi.md §"Capability calls". Host-side members (no stub): // Pool (db.* driver), Interceptors (host connect options), AppURL/MediaPath // (delivered in LoadRequest.host_config), and CoreServiceBindings (static // manifest.core_service_bindings the host mounts). RAGService. // RegisterContentFetcher is guest-side (records fetchers for HOOK_RAG_FETCH); // its Query/OnContentChanged marshal out. package caps import ( "context" "errors" "fmt" abiv1 "git.dev.alexdunmow.com/block/pluginsdk/abi/v1" "google.golang.org/protobuf/proto" ) // CallFunc is the guest→host capability transport: marshal req, invoke the // host with method ".", and unmarshal the reply into resp. It // matches wasmguest.CallHost exactly so the wasip1 shim can bind it directly. // A nil CallFunc (native builds, DESCRIBE probes) makes every capability call // fail cleanly with errNoHost rather than panic. type CallFunc func(method string, req, resp proto.Message) error // abiCoded is implemented by transport errors that carry an ABI error code // (wasmguest.HostError does). It lets the stubs map a DEADLINE_EXCEEDED reply // onto context.DeadlineExceeded without importing wasmguest. type abiCoded interface { AbiErrorCode() abiv1.AbiErrorCode } // base is embedded by every family stub: the family name and the transport. type base struct { family string call CallFunc } // invoke performs one capability call and maps any transport error with // family/method context. ctx is honored up front — an already-cancelled or // expired context short-circuits before crossing the boundary — but is not // forwarded to the transport itself (the host enforces the invoke deadline // carried in InvokeRequest.deadline_ms; see core/docs/wasm-abi.md). func (b base) invoke(ctx context.Context, method string, req, resp proto.Message) error { if b.call == nil { return fmt.Errorf("%s.%s: no host transport bound (native build)", b.family, method) } if ctx != nil { if err := ctx.Err(); err != nil { return fmt.Errorf("%s.%s: %w", b.family, method, err) } } return b.mapErr(method, b.call(b.family+"."+method, req, resp)) } // mapErr wraps a transport error with family/method context so plugin logs // stay legible, and translates an ABI deadline into context.DeadlineExceeded // so callers' errors.Is(err, context.DeadlineExceeded) keeps working. func (b base) mapErr(method string, err error) error { if err == nil { return nil } var coded abiCoded if errors.As(err, &coded) && coded.AbiErrorCode() == abiv1.AbiErrorCode_ABI_ERROR_CODE_DEADLINE_EXCEEDED { return fmt.Errorf("%s.%s: %w: %v", b.family, method, context.DeadlineExceeded, err) } return fmt.Errorf("%s.%s: %w", b.family, method, err) }