mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-08 07:21:13 +08:00
feat: add Task tool functionality
- Introduced a new `Task` presentation type in the `ToolCallStream` to handle task-related projections. - Implemented the `TaskProjection` struct with fields for description, prompt, subagent type, model, resume, and environment. - Added a `project` method to `TaskProjection` to process task-related events and generate interaction updates. - Created a `task_partial` function to format task updates for the agent server message. - Included unit tests to verify the correct behavior of task description projections.
This commit is contained in:
@@ -12,7 +12,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub use query::tool_query;
|
pub use query::tool_query;
|
||||||
pub(crate) use render::{create_plan_partial, edit_content_delta, edit_path_partial};
|
pub(crate) use render::{create_plan_partial, edit_content_delta, edit_path_partial, task_partial};
|
||||||
pub use render::{dynamic_mcp_placeholder, render_dynamic_mcp, tool_completed};
|
pub use render::{dynamic_mcp_placeholder, render_dynamic_mcp, tool_completed};
|
||||||
pub use request::{abort, mcp_request, mcp_state_request, request};
|
pub use request::{abort, mcp_request, mcp_state_request, request};
|
||||||
pub(crate) use request::{edit_read_request, json_object_to_prost, mcp_meta_request};
|
pub(crate) use request::{edit_read_request, json_object_to_prost, mcp_meta_request};
|
||||||
|
|||||||
@@ -54,6 +54,49 @@ pub(crate) fn edit_content_delta(call: &ToolCall, content: String) -> pb::AgentS
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn task_partial(
|
||||||
|
call: &ToolCall,
|
||||||
|
description: &str,
|
||||||
|
prompt: &str,
|
||||||
|
subagent: &str,
|
||||||
|
model: &str,
|
||||||
|
resume: &str,
|
||||||
|
environment: &str,
|
||||||
|
) -> pb::AgentServerMessage {
|
||||||
|
server_interaction(pb::interaction_update::Message::PartialToolCall(
|
||||||
|
pb::PartialToolCallUpdate {
|
||||||
|
call_id: call.call_id.clone(),
|
||||||
|
tool_call: Some(pb::ToolCall {
|
||||||
|
hook_additional_contexts: Vec::new(),
|
||||||
|
tool_call_id: Some(call.call_id.clone()),
|
||||||
|
started_at_ms: None,
|
||||||
|
completed_at_ms: None,
|
||||||
|
tool: Some(pb::tool_call::Tool::TaskToolCall(pb::TaskToolCall {
|
||||||
|
args: Some(pb::TaskArgs {
|
||||||
|
description: description.into(),
|
||||||
|
prompt: prompt.into(),
|
||||||
|
subagent_type: Some(subagent_type(subagent)),
|
||||||
|
model: (!model.is_empty()).then(|| model.into()),
|
||||||
|
resume: (!resume.is_empty()).then(|| resume.into()),
|
||||||
|
agent_id: None,
|
||||||
|
attachments: Vec::new(),
|
||||||
|
mode: 0,
|
||||||
|
responding_to_message_ids: Vec::new(),
|
||||||
|
environment: execution_environment(
|
||||||
|
(!environment.is_empty()).then_some(environment),
|
||||||
|
),
|
||||||
|
machine: None,
|
||||||
|
}),
|
||||||
|
result: None,
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
args_text_delta: String::new(),
|
||||||
|
model_call_id: call.model_call_id.clone(),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn create_plan_partial(
|
pub(crate) fn create_plan_partial(
|
||||||
call: &ToolCall,
|
call: &ToolCall,
|
||||||
name: &str,
|
name: &str,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ enum Presentation {
|
|||||||
DynamicMcp(pb::McpToolDefinition),
|
DynamicMcp(pb::McpToolDefinition),
|
||||||
Edit(EditProjection),
|
Edit(EditProjection),
|
||||||
CreatePlan(CreatePlanProjection),
|
CreatePlan(CreatePlanProjection),
|
||||||
|
Task(TaskProjection),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EditProjection {
|
struct EditProjection {
|
||||||
@@ -38,6 +39,17 @@ struct CreatePlanProjection {
|
|||||||
overview: String,
|
overview: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct TaskProjection {
|
||||||
|
fields: JsonStringFields,
|
||||||
|
description: String,
|
||||||
|
prompt: String,
|
||||||
|
subagent_type: String,
|
||||||
|
model: String,
|
||||||
|
resume: String,
|
||||||
|
environment: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl ToolCallStream {
|
impl ToolCallStream {
|
||||||
pub fn new(name: &str, dynamic_mcp: Option<&pb::McpToolDefinition>) -> Self {
|
pub fn new(name: &str, dynamic_mcp: Option<&pb::McpToolDefinition>) -> Self {
|
||||||
let presentation = match dynamic_mcp {
|
let presentation = match dynamic_mcp {
|
||||||
@@ -49,6 +61,7 @@ impl ToolCallStream {
|
|||||||
Presentation::Edit(EditProjection::new("target_notebook", "new_string"))
|
Presentation::Edit(EditProjection::new("target_notebook", "new_string"))
|
||||||
}
|
}
|
||||||
"createplan" => Presentation::CreatePlan(CreatePlanProjection::default()),
|
"createplan" => Presentation::CreatePlan(CreatePlanProjection::default()),
|
||||||
|
"task" => Presentation::Task(TaskProjection::default()),
|
||||||
_ => Presentation::Plain,
|
_ => Presentation::Plain,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -73,10 +86,48 @@ impl ToolCallStream {
|
|||||||
Ok(messages)
|
Ok(messages)
|
||||||
}
|
}
|
||||||
Presentation::CreatePlan(plan) => plan.project(call, raw_delta),
|
Presentation::CreatePlan(plan) => plan.project(call, raw_delta),
|
||||||
|
Presentation::Task(task) => task.project(call, raw_delta),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TaskProjection {
|
||||||
|
fn project(&mut self, call: &ToolCall, raw_delta: &str) -> Result<Vec<pb::AgentServerMessage>> {
|
||||||
|
let mut description_completed = false;
|
||||||
|
for event in self.fields.push(raw_delta)? {
|
||||||
|
match event {
|
||||||
|
StringFieldEvent::Delta { name, text } => match name.as_str() {
|
||||||
|
"description" => self.description.push_str(&text),
|
||||||
|
"prompt" => self.prompt.push_str(&text),
|
||||||
|
"subagent_type" => self.subagent_type.push_str(&text),
|
||||||
|
"model" => self.model.push_str(&text),
|
||||||
|
"resume" => self.resume.push_str(&text),
|
||||||
|
"environment" => self.environment.push_str(&text),
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
StringFieldEvent::End { name } if name == "description" => {
|
||||||
|
description_completed = true
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(description_completed
|
||||||
|
.then(|| {
|
||||||
|
interaction::task_partial(
|
||||||
|
call,
|
||||||
|
&self.description,
|
||||||
|
&self.prompt,
|
||||||
|
&self.subagent_type,
|
||||||
|
&self.model,
|
||||||
|
&self.resume,
|
||||||
|
&self.environment,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.into_iter()
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CreatePlanProjection {
|
impl CreatePlanProjection {
|
||||||
fn project(&mut self, call: &ToolCall, raw_delta: &str) -> Result<Vec<pb::AgentServerMessage>> {
|
fn project(&mut self, call: &ToolCall, raw_delta: &str) -> Result<Vec<pb::AgentServerMessage>> {
|
||||||
let mut completed_field = false;
|
let mut completed_field = false;
|
||||||
@@ -184,3 +235,53 @@ fn normalized(value: &str) -> String {
|
|||||||
.flat_map(char::to_lowercase)
|
.flat_map(char::to_lowercase)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn task_call(arguments_text: &str) -> ToolCall {
|
||||||
|
ToolCall {
|
||||||
|
index: 0,
|
||||||
|
call_id: "task-1".into(),
|
||||||
|
model_call_id: "model-1".into(),
|
||||||
|
name: "Task".into(),
|
||||||
|
arguments_text: arguments_text.into(),
|
||||||
|
arguments: serde_json::Value::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn task_description_projects_a_visible_partial_card_before_execution() {
|
||||||
|
let mut stream = ToolCallStream::new("Task", None);
|
||||||
|
let first = r#"{"description":"Review K10"#;
|
||||||
|
assert!(stream
|
||||||
|
.arguments_delta(&task_call(first), first)
|
||||||
|
.unwrap()
|
||||||
|
.is_empty());
|
||||||
|
|
||||||
|
let closing = "\",";
|
||||||
|
let messages = stream
|
||||||
|
.arguments_delta(&task_call(&format!("{first}{closing}")), closing)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(messages.len(), 1);
|
||||||
|
let Some(pb::agent_server_message::Message::InteractionUpdate(update)) =
|
||||||
|
messages[0].message.as_ref()
|
||||||
|
else {
|
||||||
|
panic!("expected interaction update")
|
||||||
|
};
|
||||||
|
let Some(pb::interaction_update::Message::PartialToolCall(partial)) =
|
||||||
|
update.message.as_ref()
|
||||||
|
else {
|
||||||
|
panic!("expected partial tool call")
|
||||||
|
};
|
||||||
|
let tool_call = partial.tool_call.as_ref().expect("expected tool call");
|
||||||
|
assert_eq!(tool_call.started_at_ms, None);
|
||||||
|
let Some(pb::tool_call::Tool::TaskToolCall(task)) = tool_call.tool.as_ref() else {
|
||||||
|
panic!("expected Task tool call")
|
||||||
|
};
|
||||||
|
let args = task.args.as_ref().expect("expected partial Task args");
|
||||||
|
assert_eq!(args.description, "Review K10");
|
||||||
|
assert_eq!(args.prompt, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user