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:
+32
-8
@@ -2,8 +2,8 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
Attribute, Fields, FnArg, ImplItem, Item, ItemImpl, ReturnType, Type, parse_macro_input,
|
||||
spanned::Spanned,
|
||||
Attribute, Fields, FnArg, ImplItem, Item, ItemImpl, ReturnType, Type, ext::IdentExt,
|
||||
parse_macro_input, spanned::Spanned,
|
||||
};
|
||||
fn docs(attrs: &[Attribute]) -> String {
|
||||
attrs
|
||||
@@ -30,6 +30,12 @@ fn documented(attrs: &[Attribute], span: proc_macro2::Span) -> syn::Result<()> {
|
||||
}
|
||||
fn serde_subset(attrs: &[Attribute]) -> syn::Result<()> {
|
||||
for a in attrs {
|
||||
if a.path().is_ident("schemars") {
|
||||
return Err(syn::Error::new(
|
||||
a.span(),
|
||||
"schemars attributes are outside the schema subset",
|
||||
));
|
||||
}
|
||||
if a.path().is_ident("serde") {
|
||||
a.parse_nested_meta(|m| {
|
||||
if !["rename", "rename_all", "default", "skip_serializing_if"]
|
||||
@@ -145,6 +151,8 @@ pub fn rpc(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let mut arms = vec![];
|
||||
let mut entries = vec![];
|
||||
let mut stop = quote!();
|
||||
let mut has_stop = false;
|
||||
let mut has_retired = false;
|
||||
for member in &mut item.items {
|
||||
let ImplItem::Fn(f) = member else { continue };
|
||||
let retired = f.attrs.iter().any(|a| {
|
||||
@@ -159,6 +167,18 @@ pub fn rpc(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
.last()
|
||||
.is_some_and(|s| s.ident == "on_stop")
|
||||
});
|
||||
if stopping && std::mem::replace(&mut has_stop, true) {
|
||||
return Err(syn::Error::new(
|
||||
f.span(),
|
||||
"only one on_stop method is allowed",
|
||||
));
|
||||
}
|
||||
if retired && std::mem::replace(&mut has_retired, true) {
|
||||
return Err(syn::Error::new(
|
||||
f.span(),
|
||||
"only one on_retired method is allowed",
|
||||
));
|
||||
}
|
||||
f.attrs.retain(|a| {
|
||||
!a.path()
|
||||
.segments
|
||||
@@ -207,7 +227,7 @@ pub fn rpc(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let wire = if retired {
|
||||
"rpc.retired".to_string()
|
||||
} else {
|
||||
name.to_string()
|
||||
name.unraw().to_string()
|
||||
};
|
||||
let p = param
|
||||
.map(|t| quote!(#t))
|
||||
@@ -237,14 +257,18 @@ pub fn rpc(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
entries.push(quote!(::sausage_plugin::method::<#p,#output>(&mut doc,#wire,#doc);));
|
||||
}
|
||||
}
|
||||
let grace =
|
||||
grace.map(|n| quote!(doc["x-sausage-grace"]=::sausage_plugin::serde_json::json!(#n);));
|
||||
let names: Vec<_> = endpoints.iter().map(|(n, _)| n.to_string()).collect();
|
||||
let grace = grace
|
||||
.map(|n| quote!(doc.value["x-sausage-grace"]=::sausage_plugin::serde_json::json!(#n);));
|
||||
let names: Vec<_> = endpoints
|
||||
.iter()
|
||||
.map(|(n, _)| n.unraw().to_string())
|
||||
.collect();
|
||||
let types: Vec<_> = endpoints.iter().map(|(_, t)| t).collect();
|
||||
Ok(quote! {#item impl ::sausage_plugin::Interface for #ty {
|
||||
fn endpoint_types(&self)->Vec<(::std::any::TypeId,String)>{vec![#((::std::any::TypeId::of::<#types>(),#names.into())),*]}
|
||||
fn methods_document()->::sausage_plugin::serde_json::Value {let mut doc=::sausage_plugin::document(env!("CARGO_PKG_NAME"),env!("CARGO_PKG_VERSION")); #(#entries)* #grace ::sausage_plugin::finish_document(doc)}
|
||||
fn describe()->::sausage_plugin::serde_json::Value {let mut doc=<Self as ::sausage_plugin::Interface>::methods_document(); #(let endpoint=<#types as ::sausage_plugin::Interface>::methods_document();doc["x-sausage-endpoints"][#names]=::sausage_plugin::serde_json::json!({"methods":endpoint["methods"]});doc["components"]["schemas"].as_object_mut().unwrap().extend(endpoint["components"]["schemas"].as_object().unwrap().clone());)* doc}
|
||||
fn add_methods(mut doc:&mut ::sausage_plugin::Discovery) {#(#entries)*}
|
||||
fn methods_document()->::sausage_plugin::serde_json::Value {let mut doc=::sausage_plugin::document(env!("CARGO_PKG_NAME"),env!("CARGO_PKG_VERSION")); Self::add_methods(&mut doc); #grace ::sausage_plugin::finish_document(doc)}
|
||||
fn describe()->::sausage_plugin::serde_json::Value {let mut doc=::sausage_plugin::document(env!("CARGO_PKG_NAME"),env!("CARGO_PKG_VERSION")); Self::add_methods(&mut doc); #grace #(doc.endpoint::<#types>(#names);)* ::sausage_plugin::finish_document(doc)}
|
||||
fn discover(&self)->::sausage_plugin::serde_json::Value {<Self as ::sausage_plugin::Interface>::describe()}
|
||||
fn dispatch<'a>(&'a self, method:&'a str, params: ::sausage_plugin::serde_json::Value,ctx:&'a ::sausage_plugin::Ctx)->::std::pin::Pin<Box<dyn ::std::future::Future<Output=::sausage_plugin::Result<::sausage_plugin::serde_json::Value>>+'a>> {Box::pin(async move {if !params.is_object() {return Err(::sausage_plugin::Error::new(-32602,"params must be an object"));} match method {#(#arms,)* _=>Err(::sausage_plugin::Error::new(-32601,"method not found"))}})}
|
||||
#stop
|
||||
|
||||
Reference in New Issue
Block a user