anveesa 0.3.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/package.json +1 -1
- package/src/provider/openai_compatible.rs +8 -8
- package/src/tui.rs +46 -11
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
package/package.json
CHANGED
|
@@ -485,24 +485,24 @@ fn parse_tool_round_limit(value: Option<&str>) -> usize {
|
|
|
485
485
|
|
|
486
486
|
fn tool_limit_message(max_tool_rounds: usize) -> Value {
|
|
487
487
|
json!({
|
|
488
|
-
"role": "
|
|
488
|
+
"role": "user",
|
|
489
489
|
"content": format!(
|
|
490
|
-
"Anveesa has already run {max_tool_rounds} tool rounds for this answer. Do not call tools again. Use the tool results already provided to produce the best final answer. If the requested work is not complete, say exactly what remains."
|
|
490
|
+
"[system: Anveesa has already run {max_tool_rounds} tool rounds for this answer. Do not call tools again. Use the tool results already provided to produce the best final answer. If the requested work is not complete, say exactly what remains.]"
|
|
491
491
|
)
|
|
492
492
|
})
|
|
493
493
|
}
|
|
494
494
|
|
|
495
495
|
fn length_continuation_message() -> Value {
|
|
496
496
|
json!({
|
|
497
|
-
"role": "
|
|
498
|
-
"content": "Your previous response was cut off because it reached the output token limit. Continue from exactly where you left off. Do not repeat text you already produced and do not restart the answer. If you were in the middle of a tool call, re-issue that complete tool call now."
|
|
497
|
+
"role": "user",
|
|
498
|
+
"content": "[system: Your previous response was cut off because it reached the output token limit. Continue from exactly where you left off. Do not repeat text you already produced and do not restart the answer. If you were in the middle of a tool call, re-issue that complete tool call now.]"
|
|
499
499
|
})
|
|
500
500
|
}
|
|
501
501
|
|
|
502
502
|
fn tool_intent_reprompt_message() -> Value {
|
|
503
503
|
json!({
|
|
504
|
-
"role": "
|
|
505
|
-
"content": "Your previous message said you would inspect/read/check the workspace, but it did not call any tool or provide a final answer. Do not narrate future tool use. If you need information, call the relevant Anveesa tools now. Otherwise, answer the user directly."
|
|
504
|
+
"role": "user",
|
|
505
|
+
"content": "[system: Your previous message said you would inspect/read/check the workspace, but it did not call any tool or provide a final answer. Do not narrate future tool use. If you need information, call the relevant Anveesa tools now. Otherwise, answer the user directly.]"
|
|
506
506
|
})
|
|
507
507
|
}
|
|
508
508
|
|
|
@@ -1136,7 +1136,7 @@ mod tests {
|
|
|
1136
1136
|
#[test]
|
|
1137
1137
|
fn length_continuation_message_asks_to_resume_without_repeating() {
|
|
1138
1138
|
let message = length_continuation_message();
|
|
1139
|
-
assert_eq!(message["role"], json!("
|
|
1139
|
+
assert_eq!(message["role"], json!("user"));
|
|
1140
1140
|
let content = message["content"].as_str().unwrap();
|
|
1141
1141
|
assert!(content.contains("cut off"));
|
|
1142
1142
|
assert!(content.contains("Do not repeat"));
|
|
@@ -1193,7 +1193,7 @@ mod tests {
|
|
|
1193
1193
|
#[test]
|
|
1194
1194
|
fn tool_limit_message_forces_final_answer() {
|
|
1195
1195
|
let message = tool_limit_message(3);
|
|
1196
|
-
assert_eq!(message["role"], json!("
|
|
1196
|
+
assert_eq!(message["role"], json!("user"));
|
|
1197
1197
|
assert!(
|
|
1198
1198
|
message["content"]
|
|
1199
1199
|
.as_str()
|
package/src/tui.rs
CHANGED
|
@@ -44,12 +44,17 @@ pub enum TuiEvent {
|
|
|
44
44
|
enum Msg {
|
|
45
45
|
User { text: String },
|
|
46
46
|
Assistant { text: String },
|
|
47
|
-
Tool {
|
|
47
|
+
Tool { done: bool, ok: bool, text: String },
|
|
48
48
|
FileOp { verb: String, path: String, added: usize, removed: usize },
|
|
49
49
|
Error(String),
|
|
50
50
|
System(String),
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
#[derive(Debug)]
|
|
54
|
+
struct PendingTool {
|
|
55
|
+
summary: String,
|
|
56
|
+
}
|
|
57
|
+
|
|
53
58
|
#[derive(Debug)]
|
|
54
59
|
struct PendingConfirm {
|
|
55
60
|
summary: String,
|
|
@@ -69,7 +74,8 @@ pub struct App {
|
|
|
69
74
|
// conversation display
|
|
70
75
|
messages: Vec<Msg>,
|
|
71
76
|
streaming_buf: String,
|
|
72
|
-
accumulated_response: String,
|
|
77
|
+
accumulated_response: String,
|
|
78
|
+
pending_tool: Option<PendingTool>, // currently-running tool (not yet committed)
|
|
73
79
|
tool_status: String,
|
|
74
80
|
plan_tasks: Vec<String>,
|
|
75
81
|
plan_done: Vec<bool>,
|
|
@@ -151,6 +157,7 @@ impl App {
|
|
|
151
157
|
messages,
|
|
152
158
|
streaming_buf: String::new(),
|
|
153
159
|
accumulated_response: String::new(),
|
|
160
|
+
pending_tool: None,
|
|
154
161
|
tool_status: String::new(),
|
|
155
162
|
plan_tasks: vec![],
|
|
156
163
|
plan_done: vec![],
|
|
@@ -636,22 +643,25 @@ async fn handle_stream_event(app: &mut App, ev: TuiEvent) {
|
|
|
636
643
|
}
|
|
637
644
|
TuiEvent::ToolCall(summary) => {
|
|
638
645
|
flush_streaming_buf(app);
|
|
639
|
-
|
|
640
|
-
app
|
|
646
|
+
// Commit any previous pending tool (shouldn't happen, but be safe)
|
|
647
|
+
commit_pending_tool(app, true);
|
|
648
|
+
app.pending_tool = Some(PendingTool { summary: summary.clone() });
|
|
649
|
+
app.tool_status = summary;
|
|
641
650
|
}
|
|
642
651
|
TuiEvent::ToolDone { summary, ok } => {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
}
|
|
652
|
+
// Commit the pending tool with its final status
|
|
653
|
+
app.pending_tool = Some(PendingTool { summary });
|
|
654
|
+
commit_pending_tool(app, ok);
|
|
647
655
|
app.tool_status = "Thinking".to_string();
|
|
648
656
|
}
|
|
649
657
|
TuiEvent::FileOp { verb, path, added, removed } => {
|
|
650
658
|
flush_streaming_buf(app);
|
|
659
|
+
commit_pending_tool(app, true);
|
|
651
660
|
app.messages.push(Msg::FileOp { verb, path, added, removed });
|
|
652
661
|
}
|
|
653
662
|
TuiEvent::Confirm { summary, reply } => {
|
|
654
663
|
flush_streaming_buf(app);
|
|
664
|
+
commit_pending_tool(app, true);
|
|
655
665
|
app.confirm = Some(PendingConfirm { summary, reply });
|
|
656
666
|
app.mode = Mode::Confirming;
|
|
657
667
|
}
|
|
@@ -688,8 +698,16 @@ fn flush_streaming_buf(app: &mut App) {
|
|
|
688
698
|
}
|
|
689
699
|
}
|
|
690
700
|
|
|
701
|
+
/// Commit a pending tool call to the message history with its final status.
|
|
702
|
+
fn commit_pending_tool(app: &mut App, ok: bool) {
|
|
703
|
+
if let Some(tool) = app.pending_tool.take() {
|
|
704
|
+
app.messages.push(Msg::Tool { done: true, ok, text: tool.summary });
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
691
708
|
/// Commit the completed turn to history and save session.
|
|
692
709
|
fn finish_turn(app: &mut App) {
|
|
710
|
+
commit_pending_tool(app, true);
|
|
693
711
|
flush_streaming_buf(app);
|
|
694
712
|
let response = std::mem::take(&mut app.accumulated_response);
|
|
695
713
|
if !response.is_empty() {
|
|
@@ -767,8 +785,14 @@ fn render_messages(frame: &mut Frame, area: Rect, app: &mut App) {
|
|
|
767
785
|
}
|
|
768
786
|
lines.push(Line::from(""));
|
|
769
787
|
}
|
|
770
|
-
Msg::Tool {
|
|
771
|
-
let color = if
|
|
788
|
+
Msg::Tool { done, ok, text } => {
|
|
789
|
+
let (icon, color) = if !done {
|
|
790
|
+
("⠋", Color::DarkGray)
|
|
791
|
+
} else if *ok {
|
|
792
|
+
("✓", Color::Rgb(152, 195, 121))
|
|
793
|
+
} else {
|
|
794
|
+
("✗", Color::Rgb(224, 108, 117))
|
|
795
|
+
};
|
|
772
796
|
lines.push(Line::from(Span::styled(
|
|
773
797
|
format!(" {icon} {text}"),
|
|
774
798
|
Style::default().fg(color),
|
|
@@ -804,8 +828,19 @@ fn render_messages(frame: &mut Frame, area: Rect, app: &mut App) {
|
|
|
804
828
|
}
|
|
805
829
|
}
|
|
806
830
|
|
|
831
|
+
// Live pending tool (running, not yet committed)
|
|
832
|
+
if let Some(tool) = &app.pending_tool {
|
|
833
|
+
let dots = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
834
|
+
let dot = dots[app.spinner_frame % dots.len()];
|
|
835
|
+
lines.push(Line::from(Span::styled(
|
|
836
|
+
format!(" {dot} {}", tool.summary),
|
|
837
|
+
Style::default().fg(Color::DarkGray),
|
|
838
|
+
)));
|
|
839
|
+
lines.push(Line::from(""));
|
|
840
|
+
}
|
|
841
|
+
|
|
807
842
|
// In-progress streaming
|
|
808
|
-
if !app.streaming_buf.is_empty() || app.mode == Mode::Streaming {
|
|
843
|
+
if !app.streaming_buf.is_empty() || (app.mode == Mode::Streaming && app.pending_tool.is_none()) {
|
|
809
844
|
lines.push(assistant_header(&app.model));
|
|
810
845
|
if !app.streaming_buf.is_empty() {
|
|
811
846
|
for l in format_assistant_lines(&app.streaming_buf, width) {
|