Compare commits

...

1 Commits

Author SHA1 Message Date
Eric Horacek
5620d7431a Add optional tool context to request_user_input
Add optional tool/arguments fields to request_user_input events and app-server tool/requestUserInput params, then wire MCP approval flows to populate and forward them.
2026-02-13 15:08:12 -08:00
16 changed files with 114 additions and 7 deletions

View File

@@ -1486,6 +1486,7 @@
},
{
"properties": {
"arguments": true,
"call_id": {
"description": "Responses API call id for the associated tool call, if available.",
"type": "string"
@@ -1496,6 +1497,12 @@
},
"type": "array"
},
"tool": {
"type": [
"string",
"null"
]
},
"turn_id": {
"default": "",
"description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.",
@@ -6086,6 +6093,7 @@
},
{
"properties": {
"arguments": true,
"call_id": {
"description": "Responses API call id for the associated tool call, if available.",
"type": "string"
@@ -6096,6 +6104,12 @@
},
"type": "array"
},
"tool": {
"type": [
"string",
"null"
]
},
"turn_id": {
"default": "",
"description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.",

View File

@@ -2064,6 +2064,7 @@
},
{
"properties": {
"arguments": true,
"call_id": {
"description": "Responses API call id for the associated tool call, if available.",
"type": "string"
@@ -2074,6 +2075,12 @@
},
"type": "array"
},
"tool": {
"type": [
"string",
"null"
]
},
"turn_id": {
"default": "",
"description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.",

View File

@@ -549,6 +549,7 @@
"ToolRequestUserInputParams": {
"description": "EXPERIMENTAL. Params sent with a request_user_input event.",
"properties": {
"arguments": true,
"itemId": {
"type": "string"
},
@@ -561,6 +562,12 @@
"threadId": {
"type": "string"
},
"tool": {
"type": [
"string",
"null"
]
},
"turnId": {
"type": "string"
}

View File

@@ -57,6 +57,7 @@
},
"description": "EXPERIMENTAL. Params sent with a request_user_input event.",
"properties": {
"arguments": true,
"itemId": {
"type": "string"
},
@@ -69,6 +70,12 @@
"threadId": {
"type": "string"
},
"tool": {
"type": [
"string",
"null"
]
},
"turnId": {
"type": "string"
}

View File

@@ -3445,6 +3445,7 @@
},
{
"properties": {
"arguments": true,
"call_id": {
"description": "Responses API call id for the associated tool call, if available.",
"type": "string"
@@ -3455,6 +3456,12 @@
},
"type": "array"
},
"tool": {
"type": [
"string",
"null"
]
},
"turn_id": {
"default": "",
"description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.",
@@ -8964,6 +8971,7 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "EXPERIMENTAL. Params sent with a request_user_input event.",
"properties": {
"arguments": true,
"itemId": {
"type": "string"
},
@@ -8976,6 +8984,12 @@
"threadId": {
"type": "string"
},
"tool": {
"type": [
"string",
"null"
]
},
"turnId": {
"type": "string"
}

View File

@@ -1486,6 +1486,7 @@
},
{
"properties": {
"arguments": true,
"call_id": {
"description": "Responses API call id for the associated tool call, if available.",
"type": "string"
@@ -1496,6 +1497,12 @@
},
"type": "array"
},
"tool": {
"type": [
"string",
"null"
]
},
"turn_id": {
"default": "",
"description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.",

View File

@@ -1486,6 +1486,7 @@
},
{
"properties": {
"arguments": true,
"call_id": {
"description": "Responses API call id for the associated tool call, if available.",
"type": "string"
@@ -1496,6 +1497,12 @@
},
"type": "array"
},
"tool": {
"type": [
"string",
"null"
]
},
"turn_id": {
"default": "",
"description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.",

View File

@@ -1486,6 +1486,7 @@
},
{
"properties": {
"arguments": true,
"call_id": {
"description": "Responses API call id for the associated tool call, if available.",
"type": "string"
@@ -1496,6 +1497,12 @@
},
"type": "array"
},
"tool": {
"type": [
"string",
"null"
]
},
"turn_id": {
"default": "",
"description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.",

View File

@@ -2,6 +2,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { RequestUserInputQuestion } from "./RequestUserInputQuestion";
import type { JsonValue } from "./serde_json/JsonValue";
export type RequestUserInputEvent = {
/**
@@ -12,4 +13,4 @@ call_id: string,
* Turn ID that this request belongs to.
* Uses `#[serde(default)]` for backwards compatibility.
*/
turn_id: string, questions: Array<RequestUserInputQuestion>, };
turn_id: string, tool: string | null, arguments: JsonValue | null, questions: Array<RequestUserInputQuestion>, };

View File

@@ -1,9 +1,10 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { JsonValue } from "../serde_json/JsonValue";
import type { ToolRequestUserInputQuestion } from "./ToolRequestUserInputQuestion";
/**
* EXPERIMENTAL. Params sent with a request_user_input event.
*/
export type ToolRequestUserInputParams = { threadId: string, turnId: string, itemId: string, questions: Array<ToolRequestUserInputQuestion>, };
export type ToolRequestUserInputParams = { threadId: string, turnId: string, itemId: string, tool: string | null, arguments: JsonValue | null, questions: Array<ToolRequestUserInputQuestion>, };

View File

@@ -2886,6 +2886,8 @@ pub struct ToolRequestUserInputParams {
pub thread_id: String,
pub turn_id: String,
pub item_id: String,
pub tool: Option<String>,
pub arguments: Option<JsonValue>,
pub questions: Vec<ToolRequestUserInputQuestion>,
}

View File

@@ -99,7 +99,7 @@ Example (from OpenAI's official VSCode extension):
- `app/list` — list available apps.
- `skills/config/write` — write user-level skill config by path.
- `mcpServer/oauth/login` — start an OAuth login for a configured MCP server; returns an `authorization_url` and later emits `mcpServer/oauthLogin/completed` once the browser flow finishes.
- `tool/requestUserInput` — prompt the user with 13 short questions for a tool call and return their answers (experimental).
- `tool/requestUserInput` — prompt the user with 13 short questions for a tool call and return their answers (experimental; may include optional `tool` and JSON `arguments` context when available).
- `config/mcpServer/reload` — reload MCP server config from disk and queue a refresh for loaded threads (applied on each thread's next active turn); returns `{}`. Use this after editing `config.toml` without restarting the server.
- `mcpServerStatus/list` — enumerate configured MCP servers with their tools, resources, resource templates, and auth status; supports cursor+limit pagination.
- `feedback/upload` — submit a feedback report (classification + optional reason/logs and conversation_id); returns the tracking thread id.

View File

@@ -299,6 +299,8 @@ pub(crate) async fn apply_bespoke_event_handling(
thread_id: conversation_id.to_string(),
turn_id: request.turn_id,
item_id: request.call_id,
tool: request.tool,
arguments: request.arguments,
questions,
};
let rx = outgoing

View File

@@ -1760,6 +1760,18 @@ impl Session {
turn_context: &TurnContext,
call_id: String,
args: RequestUserInputArgs,
) -> Option<RequestUserInputResponse> {
self.request_user_input_with_context(turn_context, call_id, args, None, None)
.await
}
pub async fn request_user_input_with_context(
&self,
turn_context: &TurnContext,
call_id: String,
args: RequestUserInputArgs,
tool: Option<String>,
arguments: Option<serde_json::Value>,
) -> Option<RequestUserInputResponse> {
let sub_id = turn_context.sub_id.clone();
let (tx_response, rx_response) = oneshot::channel();
@@ -1781,6 +1793,8 @@ impl Session {
let event = EventMsg::RequestUserInput(RequestUserInputEvent {
call_id,
turn_id: turn_context.sub_id.clone(),
tool,
arguments,
questions: args.questions,
});
self.send_event(turn_context, event).await;

View File

@@ -61,9 +61,15 @@ pub(crate) async fn handle_mcp_tool_call(
arguments: arguments_value.clone(),
};
if let Some(decision) =
maybe_request_mcp_tool_approval(sess.as_ref(), turn_context, &call_id, &server, &tool_name)
.await
if let Some(decision) = maybe_request_mcp_tool_approval(
sess.as_ref(),
turn_context,
&call_id,
&server,
&tool_name,
arguments_value.clone(),
)
.await
{
let result = match decision {
McpToolApprovalDecision::Accept | McpToolApprovalDecision::AcceptAndRemember => {
@@ -198,6 +204,7 @@ async fn maybe_request_mcp_tool_approval(
call_id: &str,
server: &str,
tool_name: &str,
arguments: Option<serde_json::Value>,
) -> Option<McpToolApprovalDecision> {
if is_full_access_mode(turn_context) {
return None;
@@ -237,7 +244,13 @@ async fn maybe_request_mcp_tool_approval(
questions: vec![question],
};
let response = sess
.request_user_input(turn_context, call_id.to_string(), args)
.request_user_input_with_context(
turn_context,
call_id.to_string(),
args,
Some(tool_name.to_string()),
arguments,
)
.await;
let decision = parse_mcp_tool_approval_response(response, &question_id);
if matches!(decision, McpToolApprovalDecision::AcceptAndRemember)

View File

@@ -51,5 +51,9 @@ pub struct RequestUserInputEvent {
/// Uses `#[serde(default)]` for backwards compatibility.
#[serde(default)]
pub turn_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<serde_json::Value>,
pub questions: Vec<RequestUserInputQuestion>,
}