mirror of
https://github.com/block/buzz.git
synced 2026-10-30 14:01:37 +00:00
## Summary Fix `reasoning_effort: Extra inputs are not permitted` for Databricks v2 model `data_workflow_tools.goose.goose-claude-opus-5-5` without dropping explicit High. - Add one provider-qualified exact capability record: Anthropic Messages, adaptive thinking, `output_config.effort`, medium upstream default. Mirror exact-record precedence in Rust/TS; keep uncurated UC services neutral and existing GPT routing unchanged. - Preserve ordered native content, including signed/empty thinking and redacted blocks, across tool results. Do not cache-stamp thinking blocks or leak native state into Chat reasoning details. Discard truncated native state; reject more than 64 native calls before any tool executes instead of altering signed content. - Add production-dispatch/ACP regressions and six shared capability vectors. No relay/ACP protocol, schema, settings, inheritance, retries, or other harness changes. ## Validation At final head `82f43f8d07bfcc3f4af492afc0a58a02f8e549c4`, clean tree: - Rebuilt real `buzz-agent`, drove ACP with a harmless MCP fixture against the actual service. Two POSTs to `/ai-gateway/anthropic/v1/messages`, both HTTP 200, exact FQN, High via `output_config`, adaptive/summarized thinking, no `reasoning_effort`. First response contained signed thinking + tool use; second request preserved signature/content order and paired result; final ACP stop `end_turn`. No credentials, signature contents, or conversation data included here. - Mandatory pre-commit/pre-push checks passed: formatting/lint, file-size gate, Rust test lanes, desktop typechecking and full JS tests, Tauri clippy/tests. - Full `cargo test -p buzz-agent` passed on the pre-commit tree (only hook formatting followed), including the new native truncation/65-call ACP regressions, ordered interleaved signed/redacted two-tool capture, nearby-ID negatives, and existing provider regressions. - Independent API/replay, regression-boundary, and Rust/TS capability reviews cleared. Full `just ci`, packaging and manual desktop UI testing were not run locally; repository CI remains the merge gate. Cross-harness internals and other UC service contracts are outside scope. ## References - [Databricks model services](https://docs.databricks.com/aws/en/ai-gateway/query-model-services) - [Anthropic thinking preservation](https://platform.claude.com/docs/en/build-with-claude/thinking#thinking-block-preservation-by-model) - [Anthropic effort](https://platform.claude.com/docs/en/build-with-claude/effort) - Originating Buzz channel: `fccdea96-0f31-4343-ba6a-324214dc8d73` - Originating thread: buzz://message?channel=fccdea96-0f31-4343-ba6a-324214dc8d73&id=33f242923a317e269c1ff7b960cacd6ec1f3371eff2f97592857804c50fb100c Signed-off-by: Wes <wesbillman@users.noreply.github.com> Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
137 lines
4.4 KiB
Rust
137 lines
4.4 KiB
Rust
//! Native replay must remain consistent with the real ACP loop's recovery limits.
|
|
mod common;
|
|
|
|
use common::{spawn_capturing_llm, Harness};
|
|
use serde_json::json;
|
|
|
|
async fn session(h: &mut Harness) -> String {
|
|
let id = h
|
|
.send(
|
|
"initialize",
|
|
json!({"protocolVersion":1,"clientCapabilities":{}}),
|
|
)
|
|
.await;
|
|
h.recv_until(|v| v["id"] == id).await;
|
|
let id = h
|
|
.send(
|
|
"session/new",
|
|
json!({"cwd":"/tmp","mcpServers":[{
|
|
"name":"probe", "command":env!("CARGO_BIN_EXE_fake-mcp"), "args":[], "env":[]
|
|
}]}),
|
|
)
|
|
.await;
|
|
h.recv_until(|v| v["id"] == id).await["result"]["sessionId"]
|
|
.as_str()
|
|
.unwrap()
|
|
.to_owned()
|
|
}
|
|
|
|
async fn native_harness(url: &str) -> Harness {
|
|
// Native Anthropic avoids Databricks discovery consuming response fixtures;
|
|
// the exact UC dispatcher and effort contract are tested in llm_fqn_tests.
|
|
Harness::spawn_with_env(
|
|
url,
|
|
&[
|
|
("BUZZ_AGENT_PROVIDER", "anthropic"),
|
|
("ANTHROPIC_API_KEY", "test"),
|
|
("ANTHROPIC_BASE_URL", url),
|
|
("BUZZ_AGENT_MODEL", "claude-opus-5-5"),
|
|
("BUZZ_AGENT_THINKING_EFFORT", "high"),
|
|
],
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn oversized_native_turn_fails_before_tools_execute() {
|
|
let mut content = vec![json!({"type":"thinking", "thinking":"", "signature":"signed"})];
|
|
content.extend((0..65).map(|n| {
|
|
json!({"type":"tool_use", "id":format!("call-{n}"),
|
|
"name":"probe__tool_0", "input":{}})
|
|
}));
|
|
let llm = spawn_capturing_llm(vec![json!({"stop_reason":"tool_use", "content":content})]).await;
|
|
let mut h = native_harness(&llm.url).await;
|
|
let sid = session(&mut h).await;
|
|
let id = h
|
|
.send(
|
|
"session/prompt",
|
|
json!({"sessionId":sid,"prompt":[{"type":"text","text":"go"}]}),
|
|
)
|
|
.await;
|
|
let mut tool_started = false;
|
|
let reply = h
|
|
.recv_until_approving(|v| {
|
|
tool_started |= v["params"]["update"]["sessionUpdate"] == "tool_call";
|
|
v["id"] == id
|
|
})
|
|
.await;
|
|
assert!(
|
|
reply["error"]
|
|
.to_string()
|
|
.contains("cannot truncate native content"),
|
|
"{reply}"
|
|
);
|
|
assert!(
|
|
!tool_started,
|
|
"oversized native turn must execute no partial subset"
|
|
);
|
|
assert_eq!(llm.captured.lock().await.len(), 1);
|
|
h.shutdown().await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn native_max_tokens_recovers_without_signed_state_or_partial_tools() {
|
|
let llm = spawn_capturing_llm(vec![
|
|
json!({"stop_reason":"max_tokens", "content":[
|
|
{"type":"thinking", "thinking":"", "signature":"incomplete-signature"},
|
|
{"type":"text", "text":"partial-text"},
|
|
{"type":"tool_use", "id":"partial-call", "name":"probe__tool_0", "input":"{broken"},
|
|
{"type":"redacted_thinking", "data":"incomplete-redaction"}
|
|
]}),
|
|
json!({"stop_reason":"end_turn", "content":[{"type":"text", "text":"done"}]}),
|
|
])
|
|
.await;
|
|
let mut h = native_harness(&llm.url).await;
|
|
let sid = session(&mut h).await;
|
|
let id = h
|
|
.send(
|
|
"session/prompt",
|
|
json!({"sessionId":sid,"prompt":[{"type":"text","text":"go"}]}),
|
|
)
|
|
.await;
|
|
let mut tool_started = false;
|
|
let reply = h
|
|
.recv_until_approving(|v| {
|
|
tool_started |= v["params"]["update"]["sessionUpdate"] == "tool_call";
|
|
v["id"] == id
|
|
})
|
|
.await;
|
|
assert_eq!(reply["result"]["stopReason"], "end_turn", "{reply}");
|
|
assert!(!tool_started);
|
|
let requests = llm.captured.lock().await;
|
|
assert_eq!(requests.len(), 2);
|
|
let retry = &requests[1]["messages"];
|
|
let assistant = retry
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.find(|m| m["role"] == "assistant")
|
|
.unwrap();
|
|
let blocks = assistant["content"].as_array().unwrap();
|
|
assert_eq!(blocks.len(), 1);
|
|
assert_eq!(blocks[0]["type"], "text");
|
|
assert_eq!(blocks[0]["text"], "partial-text");
|
|
let serialized = retry.to_string();
|
|
for forbidden in [
|
|
"incomplete-signature",
|
|
"incomplete-redaction",
|
|
"partial-call",
|
|
"tool_result",
|
|
] {
|
|
assert!(!serialized.contains(forbidden), "{retry}");
|
|
}
|
|
assert!(serialized.contains("output token limit"));
|
|
drop(requests);
|
|
h.shutdown().await;
|
|
}
|