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 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 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 headers = 7; }