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:
2026-10-23 02:38:35 +01:00
parent d7b78ed0fd
commit c728429d04
29 changed files with 829 additions and 104 deletions
+14 -3
View File
@@ -122,6 +122,9 @@ pub enum Entry {
Opaque {
/// The API shape that owns this entry.
api: String,
/// The model that produced this item. Legacy items without a model are not replayed.
#[serde(default)]
model: String,
/// The original provider object.
item: Value,
},
@@ -244,7 +247,7 @@ async fn openai(
Entry::ToolResult { id, output } => {
input.push(json!({"type": "function_call_output", "call_id": id, "output": output}))
}
Entry::Opaque { api, item } if api == "openai-responses" => input.push(item.clone()),
Entry::Opaque { api, model, item } if api == "openai-responses" && model == &cfg.model => input.push(item.clone()),
Entry::Opaque { .. } => {}
}
}
@@ -299,12 +302,17 @@ async fn openai(
}),
Some("reasoning") => out.push(Entry::Opaque {
api: "openai-responses".into(),
model: cfg.model.clone(),
item,
}),
_ => {}
}
}
if resp["status"] == "incomplete" && out.is_empty() {
if resp["status"] == "incomplete"
&& !out
.iter()
.any(|entry| matches!(entry, Entry::Assistant { text, .. } if !text.is_empty()))
{
return Err(format!(
"incomplete response: {}",
resp["incomplete_details"]
@@ -350,7 +358,9 @@ async fn anthropic(
json!({"type": "tool_result", "tool_use_id": id, "content": output}),
),
// Thinking blocks go back unchanged (with their signature): required next to tool use.
Entry::Opaque { api, item } if api == "anthropic-messages" && thinking => {
Entry::Opaque { api, model, item }
if api == "anthropic-messages" && model == &cfg.model && thinking =>
{
push("assistant", item.clone())
}
Entry::Opaque { .. } => {}
@@ -405,6 +415,7 @@ async fn anthropic(
}),
Some("thinking") | Some("redacted_thinking") => out.push(Entry::Opaque {
api: "anthropic-messages".into(),
model: cfg.model.clone(),
item: block,
}),
_ => {}