fix(#108): expose les tools MCP autorisées dès le démarrage de session Codex
This commit is contained in:
@ -700,11 +700,27 @@ mod tests {
|
||||
#[derive(Default)]
|
||||
struct FakeInvoker {
|
||||
calls: AtomicUsize,
|
||||
tools: Mutex<Vec<ToolSpec>>,
|
||||
bound_tools: Mutex<Option<Vec<ToolSpec>>>,
|
||||
}
|
||||
|
||||
impl FakeInvoker {
|
||||
fn with_bound_tools(tools: Vec<ToolSpec>) -> Self {
|
||||
Self {
|
||||
calls: AtomicUsize::new(0),
|
||||
tools: Mutex::new(Vec::new()),
|
||||
bound_tools: Mutex::new(Some(tools)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ToolInvoker for FakeInvoker {
|
||||
fn tools(&self) -> Vec<ToolSpec> {
|
||||
let tools = self.tools.lock().expect("mutex sain");
|
||||
if !tools.is_empty() {
|
||||
return tools.clone();
|
||||
}
|
||||
vec![ToolSpec {
|
||||
name: "idea_echo".to_owned(),
|
||||
description: "Echo".to_owned(),
|
||||
@ -712,6 +728,15 @@ mod tests {
|
||||
}]
|
||||
}
|
||||
|
||||
async fn tools_for_bound_context(&self) -> Result<Vec<ToolSpec>, ToolInvocationError> {
|
||||
Ok(self
|
||||
.bound_tools
|
||||
.lock()
|
||||
.expect("mutex sain")
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.tools()))
|
||||
}
|
||||
|
||||
async fn call(&self, name: &str, args_json: &str) -> Result<String, ToolInvocationError> {
|
||||
self.calls.fetch_add(1, Ordering::SeqCst);
|
||||
Ok(format!("{name}:{args_json}"))
|
||||
@ -829,6 +854,18 @@ mod tests {
|
||||
path
|
||||
}
|
||||
|
||||
fn tool(name: &str) -> ToolSpec {
|
||||
ToolSpec {
|
||||
name: name.to_owned(),
|
||||
description: format!("{name} description"),
|
||||
input_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn config(endpoint: String, max_tool_iterations: Option<u16>) -> HttpChatConfig {
|
||||
config_with_timeouts(endpoint, max_tool_iterations, 10_000, 1_000)
|
||||
}
|
||||
@ -1055,6 +1092,44 @@ mod tests {
|
||||
handle.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn first_openai_request_uses_bound_tool_surface() {
|
||||
let (endpoint, bodies, handle) = http_server(vec![TestHttpResponse::ok(
|
||||
r#"{"choices":[{"message":{"role":"assistant","content":"ok"}}]}"#,
|
||||
)])
|
||||
.await;
|
||||
let invoker = Arc::new(FakeInvoker::with_bound_tools(vec![
|
||||
tool("idea_memory_read"),
|
||||
tool("idea_run_in_background"),
|
||||
]));
|
||||
let session = OpenAiCompatibleSession::new(
|
||||
SessionId::new_random(),
|
||||
config(endpoint, Some(1)),
|
||||
temp_run_dir("initial-bound-tools"),
|
||||
"# system",
|
||||
Some(invoker),
|
||||
)
|
||||
.expect("session");
|
||||
|
||||
let _ = drain(&session, "hello").await;
|
||||
|
||||
let bodies = bodies.lock().expect("mutex sain");
|
||||
let body: Value = serde_json::from_str(&bodies[0]).expect("request body JSON");
|
||||
let names = body["tools"]
|
||||
.as_array()
|
||||
.expect("initial request carries tools")
|
||||
.iter()
|
||||
.map(|tool| {
|
||||
tool["function"]["name"]
|
||||
.as_str()
|
||||
.expect("tool function name")
|
||||
.to_owned()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(names, vec!["idea_memory_read", "idea_run_in_background"]);
|
||||
handle.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn max_tool_iterations_returns_single_degraded_final() {
|
||||
let tool = r#"{"choices":[{"message":{"role":"assistant","content":null,"tool_calls":[{"id":"call_1","type":"function","function":{"name":"idea_echo","arguments":"{}"}}]}}]}"#;
|
||||
|
||||
Reference in New Issue
Block a user