Skip to content

Commit 3a1b2bc

Browse files
fix: broken CLI formatting (#662)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent ec79d6a commit 3a1b2bc

40 files changed

+715
-863
lines changed

crates/forge_domain/src/chat_response.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{Event, ToolCallFull, ToolResult, Usage};
77
#[derive(Debug, Clone, Serialize)]
88
#[serde(rename_all = "camelCase")]
99
pub enum ChatResponse {
10-
Text(String),
10+
Text { text: String, is_complete: bool },
1111
ToolCallStart(ToolCallFull),
1212
ToolCallEnd(ToolResult),
1313
Usage(Usage),

crates/forge_domain/src/error.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub enum Error {
3939

4040
#[error("Missing description for agent: {0}")]
4141
MissingAgentDescription(AgentId),
42+
4243
#[error("Missing model for agent: {0}")]
4344
MissingModel(AgentId),
4445
}

crates/forge_domain/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ mod template;
2525
mod text_utils;
2626
mod tool;
2727
mod tool_call;
28+
mod tool_call_context;
2829
mod tool_call_parser;
2930
mod tool_choice;
3031
mod tool_definition;
@@ -59,6 +60,7 @@ pub use template::*;
5960
pub use text_utils::*;
6061
pub use tool::*;
6162
pub use tool_call::*;
63+
pub use tool_call_context::*;
6264
pub use tool_call_parser::*;
6365
pub use tool_choice::*;
6466
pub use tool_definition::*;

crates/forge_domain/src/orch.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ pub struct AgentMessage<T> {
2424
pub message: T,
2525
}
2626

27+
impl<T> AgentMessage<T> {
28+
pub fn new(agent: AgentId, message: T) -> Self {
29+
Self { agent, message }
30+
}
31+
}
32+
2733
#[derive(Clone)]
2834
pub struct Orchestrator<App> {
2935
services: Arc<App>,
@@ -89,7 +95,7 @@ impl<A: Services> Orchestrator<A> {
8995
if let Some(sender) = &self.sender {
9096
// Send message if it's a Custom type or if hide_content is false
9197
let show_text = !agent.hide_content.unwrap_or_default();
92-
let can_send = !matches!(&message, ChatResponse::Text(_)) || show_text;
98+
let can_send = !matches!(&message, ChatResponse::Text { .. }) || show_text;
9399
if can_send {
94100
sender
95101
.send(Ok(AgentMessage { agent: agent.id.clone(), message }))
@@ -163,8 +169,11 @@ impl<A: Services> Orchestrator<A> {
163169
let message = message?;
164170
messages.push(message.clone());
165171
if let Some(content) = message.content {
166-
self.send(agent, ChatResponse::Text(content.as_str().to_string()))
167-
.await?;
172+
self.send(
173+
agent,
174+
ChatResponse::Text { text: content.as_str().to_string(), is_complete: false },
175+
)
176+
.await?;
168177
}
169178

170179
if let Some(usage) = message.usage {
@@ -246,7 +255,16 @@ impl<A: Services> Orchestrator<A> {
246255
self.dispatch_spawned(event).await?;
247256
Ok(ToolResult::from(tool_call.clone()).success("Event Dispatched Successfully"))
248257
} else {
249-
Ok(self.services.tool_service().call(tool_call.clone()).await)
258+
Ok(self
259+
.services
260+
.tool_service()
261+
.call(
262+
ToolCallContext::default()
263+
.sender(self.sender.clone())
264+
.agent_id(agent.id.clone()),
265+
tool_call.clone(),
266+
)
267+
.await)
250268
}
251269
}
252270

crates/forge_domain/src/services.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use serde_json::Value;
55
use crate::{
66
Agent, Attachment, ChatCompletionMessage, Compact, Context, Conversation, ConversationId,
77
Environment, Event, EventContext, Model, ModelId, ResultStream, SystemContext, Template,
8-
ToolCallFull, ToolDefinition, ToolResult, Workflow,
8+
ToolCallContext, ToolCallFull, ToolDefinition, ToolResult, Workflow,
99
};
1010

1111
#[async_trait::async_trait]
@@ -21,7 +21,7 @@ pub trait ProviderService: Send + Sync + 'static {
2121
#[async_trait::async_trait]
2222
pub trait ToolService: Send + Sync {
2323
// TODO: should take `call` by reference
24-
async fn call(&self, call: ToolCallFull) -> ToolResult;
24+
async fn call(&self, context: ToolCallContext, call: ToolCallFull) -> ToolResult;
2525
fn list(&self) -> Vec<ToolDefinition>;
2626
fn usage_prompt(&self) -> String;
2727
}

crates/forge_domain/src/tool.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use schemars::JsonSchema;
22
use serde_json::Value;
33

4-
use crate::{ExecutableTool, NamedTool, ToolDefinition, ToolDescription};
4+
use crate::{ExecutableTool, NamedTool, ToolCallContext, ToolDefinition, ToolDescription};
55

66
struct JsonTool<T>(T);
77

@@ -18,9 +18,9 @@ where
1818
{
1919
type Input = Value;
2020

21-
async fn call(&self, input: Self::Input) -> anyhow::Result<String> {
21+
async fn call(&self, context: ToolCallContext, input: Self::Input) -> anyhow::Result<String> {
2222
let input: T::Input = serde_json::from_value(input)?;
23-
self.0.call(input).await
23+
self.0.call(context, input).await
2424
}
2525
}
2626

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
use std::sync::Arc;
2+
3+
use derive_setters::Setters;
4+
use tokio::sync::mpsc::Sender;
5+
6+
use crate::{AgentId, AgentMessage, ChatResponse};
7+
8+
/// Type alias for Arc<Sender<Result<AgentMessage<ChatResponse>>>>
9+
type ArcSender = Arc<Sender<anyhow::Result<AgentMessage<ChatResponse>>>>;
10+
11+
/// Provides additional context for tool calls.
12+
/// Currently empty but structured to allow for future extension.
13+
#[derive(Default, Clone, Debug, Setters)]
14+
pub struct ToolCallContext {
15+
#[setters(strip_option)]
16+
pub agent_id: Option<AgentId>,
17+
pub sender: Option<ArcSender>,
18+
}
19+
20+
impl ToolCallContext {
21+
/// Send a message through the sender if available
22+
pub async fn send(&self, agent_message: AgentMessage<ChatResponse>) -> anyhow::Result<()> {
23+
if let Some(sender) = &self.sender {
24+
sender.send(Ok(agent_message)).await?
25+
}
26+
Ok(())
27+
}
28+
29+
pub async fn send_text(&self, content: String) -> anyhow::Result<()> {
30+
if let Some(agent_id) = &self.agent_id {
31+
self.send(AgentMessage::new(
32+
agent_id.clone(),
33+
ChatResponse::Text { text: content.as_str().to_string(), is_complete: true },
34+
))
35+
.await
36+
} else {
37+
Ok(())
38+
}
39+
}
40+
}
41+
42+
#[cfg(test)]
43+
mod tests {
44+
use super::*;
45+
46+
#[test]
47+
fn test_create_context() {
48+
let context = ToolCallContext::default();
49+
assert!(context.sender.is_none());
50+
}
51+
52+
#[test]
53+
fn test_for_tests() {
54+
let context = ToolCallContext::default();
55+
assert!(context.sender.is_none());
56+
}
57+
58+
#[test]
59+
fn test_with_sender() {
60+
// This is just a type check test - we don't actually create a sender
61+
// as it's complex to set up in a unit test
62+
let context = ToolCallContext::default();
63+
assert!(context.sender.is_none());
64+
}
65+
}

crates/forge_domain/src/tool_definition.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use schemars::JsonSchema;
66
use serde::de::DeserializeOwned;
77
use serde::{Deserialize, Serialize};
88

9-
use crate::{NamedTool, ToolName, UsageParameterPrompt, UsagePrompt};
9+
use crate::{NamedTool, ToolCallContext, ToolName, UsageParameterPrompt, UsagePrompt};
1010

1111
///
1212
/// Refer to the specification over here:
@@ -125,5 +125,5 @@ pub trait ToolDescription {
125125
pub trait ExecutableTool {
126126
type Input: DeserializeOwned;
127127

128-
async fn call(&self, input: Self::Input) -> anyhow::Result<String>;
128+
async fn call(&self, context: ToolCallContext, input: Self::Input) -> anyhow::Result<String>;
129129
}

crates/forge_inte/tests/api_spec.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ impl Fixture {
6262
.with_context(|| "Failed to initialize chat")
6363
.unwrap()
6464
.filter_map(|message| match message.unwrap() {
65-
AgentMessage { message: ChatResponse::Text(text), .. } => Some(text),
65+
AgentMessage { message: ChatResponse::Text { text, .. }, .. } => Some(text),
6666
_ => None,
6767
})
6868
.collect::<Vec<_>>()

crates/forge_main/src/auto_update.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
use std::process::Stdio;
22

33
use anyhow::Result;
4+
use forge_tracker::EventKind;
45
use tokio::process::Command;
56

7+
use crate::TRACKER;
8+
69
/// Runs npm update in the background, failing silently
710
pub async fn update_forge() {
811
// Spawn a new task that won't block the main application
@@ -37,17 +40,9 @@ async fn perform_update() -> Result<()> {
3740

3841
/// Sends an event to the tracker when an update fails
3942
async fn send_update_failure_event(error_msg: &str) -> anyhow::Result<()> {
40-
use std::sync::OnceLock;
41-
42-
use forge_tracker::{EventKind, Tracker};
43-
44-
// Use a static tracker instance to solve the lifetime issue
45-
static TRACKER: OnceLock<Tracker> = OnceLock::new();
46-
let tracker = TRACKER.get_or_init(Tracker::default);
47-
4843
// Ignore the result since we are failing silently
4944
// This is safe because we're using a static tracker with 'static lifetime
50-
let _ = tracker
45+
let _ = TRACKER
5146
.dispatch(EventKind::Error(error_msg.to_string()))
5247
.await;
5348

crates/forge_main/src/input.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
use std::path::PathBuf;
22
use std::sync::Arc;
33

4-
use async_trait::async_trait;
54
use forge_api::Environment;
65
use forge_display::TitleFormat;
76
use tokio::fs;
87

98
use crate::console::CONSOLE;
109
use crate::editor::{ForgeEditor, ReadResult};
11-
use crate::model::{Command, ForgeCommandManager, UserInput};
10+
use crate::model::{Command, ForgeCommandManager};
1211
use crate::prompt::ForgePrompt;
12+
use crate::TRACKER;
1313

1414
/// Console implementation for handling user input via command line.
1515
#[derive(Debug)]
@@ -25,18 +25,16 @@ impl Console {
2525
}
2626
}
2727

28-
#[async_trait]
29-
impl UserInput for Console {
30-
type PromptInput = ForgePrompt;
31-
async fn upload<P: Into<PathBuf> + Send>(&self, path: P) -> anyhow::Result<Command> {
28+
impl Console {
29+
pub async fn upload<P: Into<PathBuf> + Send>(&self, path: P) -> anyhow::Result<Command> {
3230
let path = path.into();
3331
let content = fs::read_to_string(&path).await?.trim().to_string();
3432

3533
CONSOLE.writeln(content.clone())?;
3634
Ok(Command::Message(content))
3735
}
3836

39-
async fn prompt(&self, prompt: Option<Self::PromptInput>) -> anyhow::Result<Command> {
37+
pub async fn prompt(&self, prompt: Option<ForgePrompt>) -> anyhow::Result<Command> {
4038
CONSOLE.writeln("")?;
4139

4240
let mut engine = ForgeEditor::new(self.env.clone(), self.command.clone());
@@ -49,9 +47,7 @@ impl UserInput for Console {
4947
ReadResult::Exit => return Ok(Command::Exit),
5048
ReadResult::Empty => continue,
5149
ReadResult::Success(text) => {
52-
tokio::spawn(
53-
crate::ui::TRACKER.dispatch(forge_tracker::EventKind::Prompt(text.clone())),
54-
);
50+
tokio::spawn(TRACKER.dispatch(forge_tracker::EventKind::Prompt(text.clone())));
5551
match self.command.parse(&text) {
5652
Ok(command) => return Ok(command),
5753
Err(e) => {

crates/forge_main/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,8 @@ mod ui;
1414

1515
pub use auto_update::update_forge;
1616
pub use cli::Cli;
17+
use lazy_static::lazy_static;
1718
pub use ui::UI;
19+
lazy_static! {
20+
pub static ref TRACKER: forge_tracker::Tracker = forge_tracker::Tracker::default();
21+
}

crates/forge_main/src/model.rs

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
use std::path::PathBuf;
21
use std::sync::{Arc, Mutex};
32

4-
use async_trait::async_trait;
53
use forge_api::{Model, Workflow};
64
use strum::{EnumProperty, IntoEnumIterator};
75
use strum_macros::{EnumIter, EnumProperty};
@@ -256,36 +254,6 @@ impl Command {
256254
}
257255
}
258256

259-
/// A trait for handling user input in the application.
260-
///
261-
/// This trait defines the core functionality needed for processing
262-
/// user input, whether it comes from a command line interface,
263-
/// GUI, or file system.
264-
#[async_trait]
265-
pub trait UserInput {
266-
type PromptInput;
267-
/// Read content from a file and convert it to the input type.
268-
///
269-
/// # Arguments
270-
/// * `path` - The path to the file to read
271-
///
272-
/// # Returns
273-
/// * `Ok(Input)` - Successfully read and parsed file content
274-
/// * `Err` - Failed to read or parse file
275-
async fn upload<P: Into<PathBuf> + Send>(&self, path: P) -> anyhow::Result<Command>;
276-
277-
/// Prompts for user input with optional help text and initial value.
278-
///
279-
/// # Arguments
280-
/// * `help_text` - Optional help text to display with the prompt
281-
/// * `initial_text` - Optional initial text to populate the input with
282-
///
283-
/// # Returns
284-
/// * `Ok(Input)` - Successfully processed input
285-
/// * `Err` - An error occurred during input processing
286-
async fn prompt(&self, input: Option<Self::PromptInput>) -> anyhow::Result<Command>;
287-
}
288-
289257
#[cfg(test)]
290258
mod tests {
291259
use super::*;

0 commit comments

Comments
 (0)