mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
Extend the local plugin system's host<->plugin contract with generic, capability-neutral facilities, so transport plugins can be built on top without embedding plugin-specific logic in the host. Host services (plugin dials a broker-provided HostService gRPC): - per-plugin namespaced KV store (key/value size and TTL bounded) - account directory: ListAccounts + ResolveOutboundIdentity, restricted to the accounts a plugin's declared capability already covers - InitHostServices lifecycle hook; plugins that don't implement it run unchanged and are simply offered no host services Read-only runtime status channel: - HealthResponse gains an opaque, plugin-defined status_json produced passively (no config apply, no upstream call, no side effects) - GET /admin/plugins/:id/status (ungated) and the plugin.status UI-bridge verb map to Health, so a status UI can poll live state without a step-up prompt or a "test" side effect - config.test no longer force-shows a host success toast; the plugin UI owns result presentation, so plugins that read status do not spam toasts Document the UI-bridge verb taxonomy (read vs mutate/test gating) and the status contract under pkg/pluginapi.
225 lines
6.0 KiB
Protocol Buffer
225 lines
6.0 KiB
Protocol Buffer
syntax = "proto3";
|
|
|
|
package sub2api.plugin.v1;
|
|
|
|
option go_package = "github.com/Wei-Shaw/sub2api/pkg/pluginapi/v1;pluginv1";
|
|
|
|
service TransportPlugin {
|
|
rpc GetInfo(GetInfoRequest) returns (GetInfoResponse);
|
|
rpc Health(HealthRequest) returns (HealthResponse);
|
|
rpc ValidateConfig(ValidateConfigRequest) returns (ValidateConfigResponse);
|
|
rpc ApplyConfig(ApplyConfigRequest) returns (ApplyConfigResponse);
|
|
rpc TestConfig(TestConfigRequest) returns (TestConfigResponse);
|
|
rpc Forward(stream ForwardRequest) returns (stream ForwardResponse);
|
|
// InitHostServices hands the plugin a go-plugin broker stream id it can dial
|
|
// back to reach the host-provided HostService. It is an optional, generic
|
|
// lifecycle hook: plugins built against an older contract leave it
|
|
// Unimplemented and the host simply runs without offering host services.
|
|
rpc InitHostServices(InitHostServicesRequest) returns (InitHostServicesResponse);
|
|
}
|
|
|
|
// HostService is implemented by the host and dialed by the plugin over the
|
|
// go-plugin broker id delivered via InitHostServices. It exposes generic,
|
|
// capability-neutral facilities that any plugin may use; new facilities are
|
|
// added here as new RPCs without changing the transport contract version.
|
|
service HostService {
|
|
rpc KVGet(KVGetRequest) returns (KVGetResponse);
|
|
rpc KVSet(KVSetRequest) returns (KVSetResponse);
|
|
rpc KVDelete(KVDeleteRequest) returns (KVDeleteResponse);
|
|
rpc KVList(KVListRequest) returns (KVListResponse);
|
|
// Account directory: enumerate the accounts the plugin is bound to act on and
|
|
// resolve the outbound identity (credentials/headers/proxy) the host would
|
|
// attach to a live request. The host restricts both calls to accounts the
|
|
// requesting plugin's capability already covers.
|
|
rpc ListAccounts(ListAccountsRequest) returns (ListAccountsResponse);
|
|
rpc ResolveOutboundIdentity(ResolveOutboundIdentityRequest) returns (ResolveOutboundIdentityResponse);
|
|
}
|
|
|
|
message GetInfoRequest {}
|
|
|
|
message GetInfoResponse {
|
|
string plugin_id = 1;
|
|
string plugin_version = 2;
|
|
uint32 protocol_version = 3;
|
|
uint32 transport_api_version = 4;
|
|
repeated string capabilities = 5;
|
|
}
|
|
|
|
message HealthRequest {}
|
|
|
|
message HealthResponse {
|
|
bool healthy = 1;
|
|
string message = 2;
|
|
// status_json is an optional plugin-defined JSON blob describing current runtime
|
|
// state (e.g. a status summary for the config UI). It MUST be produced passively
|
|
// — reading it must not apply config, reach upstream, or cause side effects — so
|
|
// the host can expose it via a lightweight, ungated status endpoint. Ignored by
|
|
// the host's health gating.
|
|
string status_json = 3;
|
|
}
|
|
|
|
message ValidateConfigRequest {
|
|
bytes config_json = 1;
|
|
}
|
|
|
|
message ValidateConfigResponse {
|
|
bool valid = 1;
|
|
string message = 2;
|
|
bytes normalized_config_json = 3;
|
|
}
|
|
|
|
message ApplyConfigRequest {
|
|
bytes config_json = 1;
|
|
}
|
|
|
|
message ApplyConfigResponse {
|
|
bool applied = 1;
|
|
string message = 2;
|
|
}
|
|
|
|
message TestConfigRequest {
|
|
bytes config_json = 1;
|
|
}
|
|
|
|
message TestConfigResponse {
|
|
bool success = 1;
|
|
string message = 2;
|
|
int64 latency_ms = 3;
|
|
// status_json is an optional plugin-defined JSON blob surfaced to the config UI
|
|
// alongside the test result (e.g. a runtime status summary). Ignored by the host.
|
|
string status_json = 4;
|
|
}
|
|
|
|
message HeaderValues {
|
|
repeated string values = 1;
|
|
}
|
|
|
|
message ForwardRequestStart {
|
|
string request_id = 1;
|
|
string method = 2;
|
|
string url = 3;
|
|
string host = 4;
|
|
map<string, HeaderValues> headers = 5;
|
|
string proxy_url = 6;
|
|
int64 account_id = 7;
|
|
int32 account_concurrency = 8;
|
|
string platform = 9;
|
|
string account_type = 10;
|
|
int64 content_length = 11;
|
|
bool has_body = 12;
|
|
}
|
|
|
|
message ForwardRequest {
|
|
oneof frame {
|
|
ForwardRequestStart start = 1;
|
|
bytes body_chunk = 2;
|
|
bool body_end = 3;
|
|
}
|
|
}
|
|
|
|
message ForwardResponseStart {
|
|
int32 status_code = 1;
|
|
string status = 2;
|
|
string protocol = 3;
|
|
int32 protocol_major = 4;
|
|
int32 protocol_minor = 5;
|
|
map<string, HeaderValues> headers = 6;
|
|
int64 content_length = 7;
|
|
}
|
|
|
|
message ForwardResponseEnd {
|
|
int64 bytes_received = 1;
|
|
int64 duration_ms = 2;
|
|
}
|
|
|
|
message ForwardResponseError {
|
|
string code = 1;
|
|
string message = 2;
|
|
bool request_sent = 3;
|
|
}
|
|
|
|
message ForwardResponse {
|
|
oneof frame {
|
|
ForwardResponseStart start = 1;
|
|
bytes body_chunk = 2;
|
|
ForwardResponseEnd end = 3;
|
|
ForwardResponseError error = 4;
|
|
}
|
|
}
|
|
|
|
message InitHostServicesRequest {
|
|
// host_service_id is the broker stream id the plugin dials to reach HostService.
|
|
uint32 host_service_id = 1;
|
|
// host_service_api_version lets the plugin reject a host it cannot talk to.
|
|
uint32 host_service_api_version = 2;
|
|
}
|
|
|
|
message InitHostServicesResponse {
|
|
// ready is true once the plugin has successfully dialed back the host service.
|
|
bool ready = 1;
|
|
string message = 2;
|
|
}
|
|
|
|
// KV messages back a namespaced, per-plugin persistent key-value store. The
|
|
// host derives the owning plugin from the runtime that serves the connection,
|
|
// so a plugin can never read or write another plugin's namespace.
|
|
message KVGetRequest {
|
|
string namespace = 1;
|
|
string key = 2;
|
|
}
|
|
|
|
message KVGetResponse {
|
|
bool found = 1;
|
|
bytes value = 2;
|
|
}
|
|
|
|
message KVSetRequest {
|
|
string namespace = 1;
|
|
string key = 2;
|
|
bytes value = 3;
|
|
// ttl_seconds > 0 sets an expiry; 0 stores the entry without expiry.
|
|
int64 ttl_seconds = 4;
|
|
}
|
|
|
|
message KVSetResponse {}
|
|
|
|
message KVDeleteRequest {
|
|
string namespace = 1;
|
|
string key = 2;
|
|
}
|
|
|
|
message KVDeleteResponse {}
|
|
|
|
message KVListRequest {
|
|
string namespace = 1;
|
|
string key_prefix = 2;
|
|
int32 limit = 3;
|
|
}
|
|
|
|
message KVListResponse {
|
|
repeated string keys = 1;
|
|
}
|
|
|
|
message ListAccountsRequest {
|
|
string platform = 1;
|
|
string account_type = 2;
|
|
}
|
|
|
|
message ListAccountsResponse {
|
|
repeated int64 account_ids = 1;
|
|
}
|
|
|
|
message ResolveOutboundIdentityRequest {
|
|
int64 account_id = 1;
|
|
}
|
|
|
|
message ResolveOutboundIdentityResponse {
|
|
bool found = 1;
|
|
int64 account_id = 2;
|
|
string platform = 3;
|
|
string account_type = 4;
|
|
string proxy_url = 5;
|
|
string token = 6;
|
|
map<string, HeaderValues> headers = 7;
|
|
}
|