sdk, agent: review fixes (T18R)
- The serve loop ends the process after EOF or exit_after_response, also with background tasks or listeners. - One schema generator per direction for the whole document, so types with the same name get distinct names. - The agent gives the complete error (code, message, data) to the model, writes SKIP only after a successful request, caps tool output at 64 KiB, writes each history line in one write, and skips a damaged line. - Endpoints skip blank and non-RPC lines. client_rs does not panic. - #[must_use] on builders and the lock guard; #[schemars] attributes and duplicate hooks are compile errors; raw identifiers; -32000 for local SDK errors; x-sausage-params only for methods with params.
This commit is contained in:
+15
-17
@@ -7,6 +7,9 @@ use serde_json::json;
|
||||
use std::{future::IntoFuture, marker::PhantomData, path::Path, rc::Rc};
|
||||
|
||||
/// A verified endpoint address and identity.
|
||||
///
|
||||
/// Connect verifies and closes one connection. Each call opens a new connection
|
||||
/// and repeats the identity handshake before its request. The first call costs three round trips.
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(not(target_os = "wasi"), allow(dead_code))]
|
||||
pub struct Endpoint {
|
||||
@@ -20,7 +23,7 @@ impl Endpoint {
|
||||
if self.interface == name {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(-32002, "endpoint interface mismatch"))
|
||||
Err(Error::new(-32000, "endpoint interface mismatch"))
|
||||
}
|
||||
}
|
||||
/// Build a typed endpoint request.
|
||||
@@ -35,6 +38,7 @@ impl Endpoint {
|
||||
}
|
||||
}
|
||||
/// A lazy endpoint request with optional progress callbacks.
|
||||
#[must_use = "await the endpoint call to send it"]
|
||||
pub struct EndpointCall<T> {
|
||||
endpoint: Endpoint,
|
||||
method: String,
|
||||
@@ -87,7 +91,7 @@ pub async fn listen<T: Interface>(path: impl AsRef<Path>, value: Rc<T>) -> Resul
|
||||
#[cfg(not(target_os = "wasi"))]
|
||||
pub async fn listen<T: Interface>(_path: impl AsRef<Path>, _value: Rc<T>) -> Result<()> {
|
||||
crate::transport::endpoint_name::<T>()?;
|
||||
Err(Error::new(-32002, "endpoints require WASI"))
|
||||
Err(Error::new(-32000, "endpoints require WASI"))
|
||||
}
|
||||
|
||||
/// Read an endpoint file and verify its owner before returning a client.
|
||||
@@ -122,7 +126,7 @@ pub async fn connect(path: impl AsRef<Path>) -> Result<Endpoint> {
|
||||
/// Connect to an endpoint. Native programs require the WASI runtime.
|
||||
#[cfg(not(target_os = "wasi"))]
|
||||
pub async fn connect(_path: impl AsRef<Path>) -> Result<Endpoint> {
|
||||
Err(Error::new(-32002, "endpoints require WASI"))
|
||||
Err(Error::new(-32000, "endpoints require WASI"))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "wasi")]
|
||||
@@ -139,11 +143,10 @@ async fn handshake(endpoint: &Endpoint) -> Result<wstd::net::TcpStream> {
|
||||
json!({"jsonrpc":"2.0","id":0,"method":"rpc.endpoint","params":{}}),
|
||||
)
|
||||
.await?;
|
||||
let bytes = crate::lines::Lines::default()
|
||||
.next(&mut stream)
|
||||
let reply = crate::lines::Lines::default()
|
||||
.rpc(&mut stream)
|
||||
.await?
|
||||
.ok_or_else(|| Error::new(-32002, "endpoint closed"))?;
|
||||
let reply: Value = serde_json::from_slice(&bytes)?;
|
||||
.ok_or_else(|| Error::new(-32000, "endpoint closed"))?;
|
||||
let result = &reply["result"];
|
||||
let plugin = endpoint
|
||||
.owner
|
||||
@@ -154,7 +157,7 @@ async fn handshake(endpoint: &Endpoint) -> Result<wstd::net::TcpStream> {
|
||||
|| result["interface"] != endpoint.interface
|
||||
|| result["plugin"].as_str() != plugin
|
||||
{
|
||||
return Err(Error::new(-32002, "endpoint identity mismatch"));
|
||||
return Err(Error::new(-32000, "endpoint identity mismatch"));
|
||||
}
|
||||
Ok(stream)
|
||||
}
|
||||
@@ -172,8 +175,7 @@ async fn endpoint_request(
|
||||
json!({"jsonrpc":"2.0","id":1,"method":method,"params":params}),
|
||||
)
|
||||
.await?;
|
||||
while let Some(bytes) = lines.next(&mut stream).await? {
|
||||
let value: Value = serde_json::from_slice(&bytes)?;
|
||||
while let Some(value) = lines.rpc(&mut stream).await? {
|
||||
if value["method"] == "core.notify" {
|
||||
if let Some(f) = &mut notify {
|
||||
f(
|
||||
@@ -188,7 +190,7 @@ async fn endpoint_request(
|
||||
return Ok(value["result"].clone());
|
||||
}
|
||||
}
|
||||
Err(Error::new(-32002, "endpoint closed before response"))
|
||||
Err(Error::new(-32000, "endpoint closed before response"))
|
||||
}
|
||||
#[cfg(not(target_os = "wasi"))]
|
||||
async fn endpoint_request(
|
||||
@@ -197,7 +199,7 @@ async fn endpoint_request(
|
||||
_params: Value,
|
||||
_notify: Option<Notify>,
|
||||
) -> Result<Value> {
|
||||
Err(Error::new(-32002, "endpoints require WASI"))
|
||||
Err(Error::new(-32000, "endpoints require WASI"))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "wasi")]
|
||||
@@ -209,11 +211,7 @@ async fn serve_connection<T: Interface>(
|
||||
use futures::{FutureExt, StreamExt};
|
||||
let (mut reader, mut writer) = stream.split();
|
||||
let mut lines = crate::lines::Lines::default();
|
||||
while let Some(bytes) = lines.next(&mut reader).await? {
|
||||
let request: Value = serde_json::from_slice(&bytes)?;
|
||||
if request["jsonrpc"] != "2.0" {
|
||||
continue;
|
||||
}
|
||||
while let Some(request) = lines.rpc(&mut reader).await? {
|
||||
let method = request["method"].as_str().unwrap_or_default();
|
||||
let (tx, mut rx) = futures::channel::mpsc::unbounded();
|
||||
let ctx = Ctx {
|
||||
|
||||
Reference in New Issue
Block a user