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:
+45
-4
@@ -1,22 +1,34 @@
|
||||
//! Buffered newline framing shared by stdin and endpoint connections.
|
||||
#[cfg(target_os = "wasi")]
|
||||
#[cfg(any(target_os = "wasi", test))]
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Lines {
|
||||
bytes: Vec<u8>,
|
||||
scanned: usize,
|
||||
}
|
||||
#[cfg(target_os = "wasi")]
|
||||
#[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(end) = self.bytes.iter().position(|b| *b == b'\n') {
|
||||
return Ok(Some(self.bytes.drain(..=end).collect()));
|
||||
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 {
|
||||
@@ -26,4 +38,33 @@ impl Lines {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user