Skip to main content

netget/server/grpc_web/
actions.rs

1use crate::{
2    llm::actions::{
3        protocol_trait::{ActionResult, Protocol, Server},
4        ActionDefinition, ParameterDefinition, StartupExamples,
5    },
6    protocol::{EventType, SpawnContext},
7    state::AppState,
8};
9use anyhow::Result;
10use serde_json::{json, Value};
11#[derive(Default)]
12pub struct GrpcWebProtocol;
13impl GrpcWebProtocol {
14    pub fn new() -> Self {
15        Self
16    }
17}
18impl Protocol for GrpcWebProtocol {
19    fn protocol_name(&self) -> &'static str {
20        "gRPC-Web"
21    }
22    fn stack_name(&self) -> &'static str {
23        "ETH>IP>TCP>HTTP>GRPC-WEB"
24    }
25    fn group_name(&self) -> &'static str {
26        "Application"
27    }
28    fn description(&self) -> &'static str {
29        "Binary protobuf HTTP/1.1 unary and server-streaming binding"
30    }
31    fn example_prompt(&self) -> &'static str {
32        "Serve a protobuf greeting to a gRPC-Web client"
33    }
34    fn keywords(&self) -> Vec<&'static str> {
35        vec!["grpc-web", "grpcweb", "grpc web"]
36    }
37    fn get_async_actions(&self, state: &AppState) -> Vec<ActionDefinition> {
38        crate::server::grpc::actions::GrpcProtocol::new().get_async_actions(state)
39    }
40    fn get_sync_actions(&self) -> Vec<ActionDefinition> {
41        crate::server::grpc::actions::GrpcProtocol::new()
42            .get_sync_actions()
43            .into_iter()
44            .filter(|action| action.name != "grpc_unary_response")
45            .collect()
46    }
47    fn get_event_types(&self) -> Vec<EventType> {
48        crate::server::grpc::actions::GrpcProtocol::new()
49            .get_event_types()
50            .into_iter()
51            .filter(|event| matches!(event.id.as_str(), "grpc_stream_opened" | "grpc_stream_tick"))
52            .collect()
53    }
54    fn get_startup_parameters(&self) -> Vec<ParameterDefinition> {
55        let mut parameters =
56            crate::server::grpc::actions::GrpcProtocol::new().get_startup_parameters();
57        parameters.retain(|parameter| parameter.name == "proto_schema");
58        parameters.extend([
59            ParameterDefinition { name:"rpc_timeout_secs".into(), type_hint:"integer".into(), required:false,
60                description:"Whole RPC deadline 1..3600 seconds, shortened by grpc-timeout; includes response backpressure".into(),
61                example:json!(300), default:Some(json!(super::DEFAULT_RPC_TIMEOUT_SECS)) },
62            ParameterDefinition { name:"allow_origin".into(), type_hint:"string".into(), required:false,
63                description:"One exact HTTP(S) browser origin, without trailing slash; omitted rejects all Origin headers. No credentials or wildcard".into(),
64                example:json!("https://app.example"), default:None },
65        ]);
66        parameters
67    }
68    fn get_dependencies(&self) -> Vec<crate::protocol::dependencies::ProtocolDependency> {
69        crate::server::grpc::actions::GrpcProtocol::new().get_dependencies()
70    }
71    fn startup_dependencies(
72        &self,
73        params: Option<&Value>,
74    ) -> Vec<crate::protocol::dependencies::ProtocolDependency> {
75        crate::server::grpc::actions::GrpcProtocol::new().startup_dependencies(params)
76    }
77    fn metadata(&self) -> crate::protocol::metadata::ProtocolMetadataV2 {
78        use crate::protocol::metadata::{DevelopmentState, ProtocolMetadataV2};
79        ProtocolMetadataV2::builder().state(DevelopmentState::Experimental)
80            .implementation("Separately registered binary protobuf gRPC-Web over HTTP/1.1. tonic-web0.12.3 frames responses; tonic0.12.3 and shared bounded DynamicCodec decode messages.")
81            .llm_control("Typed stream-open and tick events for unary and server-streaming methods; explicit send/finish/cancel/wait/error. No application storage.")
82            .e2e_testing("Mandatory pinned independent Connect-ES peers in both roles, NetGet pairs and bounded framing/cancellation tests.")
83            .notes("Binary only: text mode is rejected. Client streaming, bidi, reflection, WebSocket, receiver TLS, browser execution, pcap and fuzz evidence are outside this Experimental scope. CORS requires one explicitly configured origin; no credentials.")
84            .max_inbound_bytes(super::wire::MAX_MESSAGE_BYTES + 5).build()
85    }
86    fn get_startup_examples(&self) -> StartupExamples {
87        let schema = "syntax=\"proto3\"; package demo; message Greeting { string name=1; } service Greeter { rpc Hello(Greeting) returns (Greeting); }";
88        let base = |handler: Option<Value>, instruction: &str| {
89            let mut value = json!({"type":"open_server","base_stack":"grpc-web","port":8080,"startup_params":{"proto_schema":schema},"instruction":instruction});
90            if let Some(handler) = handler {
91                value["event_handlers"] =
92                    json!([{"event_pattern":"grpc_stream_opened","handler":handler}]);
93            }
94            value
95        };
96        StartupExamples::new(
97            base(None, "On grpc_stream_opened, greet the request's message.name using grpc_stream_send with message.name, then grpc_stream_finish."),
98            base(Some(json!({"type":"script","language":"python","code":"import json,sys\ne=json.load(sys.stdin)['event']\nprint(json.dumps({'actions':[{'type':'grpc_stream_send','message':{'name':'Hello '+e['message']['name']}},{'type':'grpc_stream_finish'}]}))"})), ""),
99            base(Some(json!({"type":"static","actions":[{"type":"grpc_stream_send","message":{"name":"Hello"}},{"type":"grpc_stream_finish"}]})), ""),
100        )
101    }
102}
103impl Server for GrpcWebProtocol {
104    fn spawn(
105        &self,
106        ctx: SpawnContext,
107    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<std::net::SocketAddr>> + Send>>
108    {
109        Box::pin(super::GrpcWebServer::spawn(ctx))
110    }
111    fn execute_action(&self, action: Value) -> Result<ActionResult> {
112        anyhow::ensure!(
113            action["type"] != "grpc_unary_response",
114            "use grpc_stream_send and grpc_stream_finish for gRPC-Web unary replies"
115        );
116        crate::server::grpc::actions::GrpcProtocol::new().execute_action(action)
117    }
118}