- 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.
71 lines
2.2 KiB
Rust
71 lines
2.2 KiB
Rust
//! Buffered newline framing shared by stdin and endpoint connections.
|
|
#[cfg(any(target_os = "wasi", test))]
|
|
#[derive(Default)]
|
|
pub(crate) struct Lines {
|
|
bytes: Vec<u8>,
|
|
scanned: usize,
|
|
}
|
|
#[cfg(any(target_os = "wasi", test))]
|
|
impl Lines {
|
|
fn frame(&mut self) -> Option<Vec<u8>> {
|
|
if let Some(end) = self.bytes[self.scanned..].iter().position(|b| *b == b'\n') {
|
|
let end = self.scanned + end;
|
|
self.scanned = 0;
|
|
return Some(self.bytes.drain(..=end).collect());
|
|
}
|
|
self.scanned = self.bytes.len();
|
|
None
|
|
}
|
|
#[cfg(target_os = "wasi")]
|
|
pub(crate) async fn next(
|
|
&mut self,
|
|
input: &mut impl wstd::io::AsyncRead,
|
|
) -> std::io::Result<Option<Vec<u8>>> {
|
|
loop {
|
|
if let Some(line) = self.frame() {
|
|
return Ok(Some(line));
|
|
}
|
|
let mut buffer = [0; 8192];
|
|
let count = input.read(&mut buffer).await?;
|
|
if count == 0 {
|
|
self.scanned = 0;
|
|
return Ok(if self.bytes.is_empty() {
|
|
None
|
|
} else {
|
|
Some(std::mem::take(&mut self.bytes))
|
|
});
|
|
}
|
|
self.bytes.extend_from_slice(&buffer[..count]);
|
|
}
|
|
}
|
|
#[cfg(target_os = "wasi")]
|
|
pub(crate) async fn rpc(
|
|
&mut self,
|
|
input: &mut impl wstd::io::AsyncRead,
|
|
) -> std::io::Result<Option<serde_json::Value>> {
|
|
while let Some(bytes) = self.next(input).await? {
|
|
if let Ok(value) = serde_json::from_slice::<serde_json::Value>(&bytes)
|
|
&& value.is_object()
|
|
&& value["jsonrpc"] == "2.0"
|
|
{
|
|
return Ok(Some(value));
|
|
}
|
|
}
|
|
Ok(None)
|
|
}
|
|
}
|
|
#[cfg(test)]
|
|
mod tests {
|
|
#[test]
|
|
fn incomplete_frames_retain_the_scanned_length() {
|
|
let mut lines = super::Lines::default();
|
|
lines.bytes.extend_from_slice(b"first");
|
|
assert!(lines.frame().is_none());
|
|
assert_eq!(lines.scanned, 5);
|
|
lines.bytes.extend_from_slice(b"\nsecond\n");
|
|
assert_eq!(lines.frame().unwrap(), b"first\n");
|
|
assert_eq!(lines.frame().unwrap(), b"second\n");
|
|
assert!(lines.frame().is_none());
|
|
}
|
|
}
|