Skip to content

Commit 62e1120

Browse files
feat(env): support auto-dumping conversation via FORGE_AUTO_DUMP env var (#2441)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent e587cb5 commit 62e1120

4 files changed

Lines changed: 130 additions & 1 deletion

File tree

crates/forge_app/src/orch_spec/orch_setup.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ impl Default for TestContext {
8585
stdout_max_line_length: 200, // 5 MB
8686
max_line_length: 2000,
8787
auto_open_dump: false,
88+
auto_dump: None,
8889
debug_requests: None,
8990
custom_history_path: None,
9091
max_conversations: 100,

crates/forge_domain/src/env.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::hash::{DefaultHasher, Hash, Hasher};
22
use std::path::PathBuf;
3+
use std::str::FromStr;
34

45
use derive_more::Display;
56
use derive_setters::Setters;
@@ -99,6 +100,33 @@ pub struct Environment {
99100
/// Maximum number of file extensions to include in the system prompt.
100101
/// Controlled by FORGE_MAX_EXTENSIONS environment variable.
101102
pub max_extensions: usize,
103+
/// Format for automatically creating a dump when a task is completed.
104+
/// Controlled by FORGE_AUTO_DUMP environment variable.
105+
/// Set to "json" (or "true"/"1"/"yes") for JSON, "html" for HTML, or
106+
/// unset/other to disable.
107+
pub auto_dump: Option<AutoDumpFormat>,
108+
}
109+
110+
/// The output format used when auto-dumping a conversation on task completion.
111+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, fake::Dummy)]
112+
#[serde(rename_all = "camelCase")]
113+
pub enum AutoDumpFormat {
114+
/// Dump as a JSON file.
115+
Json,
116+
/// Dump as an HTML file.
117+
Html,
118+
}
119+
120+
impl FromStr for AutoDumpFormat {
121+
type Err = ();
122+
123+
fn from_str(s: &str) -> Result<Self, Self::Err> {
124+
match s.to_lowercase().as_str() {
125+
"html" => Ok(AutoDumpFormat::Html),
126+
"json" | "true" | "1" | "yes" => Ok(AutoDumpFormat::Json),
127+
_ => Err(()),
128+
}
129+
}
102130
}
103131

104132
impl Environment {
@@ -316,6 +344,7 @@ fn test_command_path() {
316344
override_model: None,
317345
override_provider: None,
318346
max_extensions: 15,
347+
auto_dump: None,
319348
};
320349

321350
let actual = fixture.command_path();
@@ -358,6 +387,7 @@ fn test_command_cwd_path() {
358387
override_model: None,
359388
override_provider: None,
360389
max_extensions: 15,
390+
auto_dump: None,
361391
};
362392

363393
let actual = fixture.command_cwd_path();
@@ -400,6 +430,7 @@ fn test_command_cwd_path_independent_from_command_path() {
400430
override_model: None,
401431
override_provider: None,
402432
max_extensions: 15,
433+
auto_dump: None,
403434
};
404435

405436
let command_path = fixture.command_path();

crates/forge_infra/src/env.rs

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use std::path::{Path, PathBuf};
33
use std::str::FromStr;
44

55
use forge_app::EnvironmentInfra;
6-
use forge_domain::{Environment, ModelId, ProviderId, RetryConfig, TlsBackend, TlsVersion};
6+
use forge_domain::{
7+
AutoDumpFormat, Environment, ModelId, ProviderId, RetryConfig, TlsBackend, TlsVersion,
8+
};
79
use reqwest::Url;
810

911
#[derive(Clone)]
@@ -102,6 +104,7 @@ impl ForgeEnvironmentInfra {
102104
override_model,
103105
override_provider,
104106
max_extensions: parse_env::<usize>("FORGE_MAX_EXTENSIONS").unwrap_or(15),
107+
auto_dump: parse_env::<AutoDumpFormat>("FORGE_AUTO_DUMP"),
105108
}
106109
}
107110

@@ -183,6 +186,7 @@ impl_from_env_str_via_from_str! {
183186
String,
184187
forge_domain::TlsBackend,
185188
forge_domain::TlsVersion,
189+
forge_domain::AutoDumpFormat,
186190
}
187191

188192
/// Parse environment variable using custom FromEnvStr trait
@@ -633,6 +637,95 @@ mod tests {
633637
}
634638
}
635639

640+
#[test]
641+
#[serial]
642+
fn test_auto_dump_env_var() {
643+
use forge_domain::AutoDumpFormat;
644+
let cwd = tempdir().unwrap().path().to_path_buf();
645+
let infra = ForgeEnvironmentInfra::new(false, cwd);
646+
647+
// Test default value when env var is not set
648+
{
649+
unsafe {
650+
env::remove_var("FORGE_AUTO_DUMP");
651+
}
652+
let env = infra.get_environment();
653+
assert_eq!(env.auto_dump, None);
654+
}
655+
656+
// Test JSON with "json"
657+
{
658+
unsafe {
659+
env::set_var("FORGE_AUTO_DUMP", "json");
660+
}
661+
let env = infra.get_environment();
662+
assert_eq!(env.auto_dump, Some(AutoDumpFormat::Json));
663+
unsafe {
664+
env::remove_var("FORGE_AUTO_DUMP");
665+
}
666+
}
667+
668+
// Test JSON with "true"
669+
{
670+
unsafe {
671+
env::set_var("FORGE_AUTO_DUMP", "true");
672+
}
673+
let env = infra.get_environment();
674+
assert_eq!(env.auto_dump, Some(AutoDumpFormat::Json));
675+
unsafe {
676+
env::remove_var("FORGE_AUTO_DUMP");
677+
}
678+
}
679+
680+
// Test JSON with "1"
681+
{
682+
unsafe {
683+
env::set_var("FORGE_AUTO_DUMP", "1");
684+
}
685+
let env = infra.get_environment();
686+
assert_eq!(env.auto_dump, Some(AutoDumpFormat::Json));
687+
unsafe {
688+
env::remove_var("FORGE_AUTO_DUMP");
689+
}
690+
}
691+
692+
// Test HTML with "html"
693+
{
694+
unsafe {
695+
env::set_var("FORGE_AUTO_DUMP", "html");
696+
}
697+
let env = infra.get_environment();
698+
assert_eq!(env.auto_dump, Some(AutoDumpFormat::Html));
699+
unsafe {
700+
env::remove_var("FORGE_AUTO_DUMP");
701+
}
702+
}
703+
704+
// Test HTML case-insensitive "HTML"
705+
{
706+
unsafe {
707+
env::set_var("FORGE_AUTO_DUMP", "HTML");
708+
}
709+
let env = infra.get_environment();
710+
assert_eq!(env.auto_dump, Some(AutoDumpFormat::Html));
711+
unsafe {
712+
env::remove_var("FORGE_AUTO_DUMP");
713+
}
714+
}
715+
716+
// Test disabled with invalid value
717+
{
718+
unsafe {
719+
env::set_var("FORGE_AUTO_DUMP", "invalid");
720+
}
721+
let env = infra.get_environment();
722+
assert_eq!(env.auto_dump, None);
723+
unsafe {
724+
env::remove_var("FORGE_AUTO_DUMP");
725+
}
726+
}
727+
}
728+
636729
#[test]
637730
#[serial]
638731
fn test_tool_timeout_env_var() {

crates/forge_main/src/ui.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2883,6 +2883,10 @@ impl<A: API + ConsoleWriter + 'static, F: Fn() -> A + Send + Sync> UI<A, F> {
28832883
TitleFormat::debug("Finished").sub_title(conversation_id.into_string()),
28842884
)?;
28852885
}
2886+
if let Some(format) = self.api.environment().auto_dump {
2887+
let html = matches!(format, forge_domain::AutoDumpFormat::Html);
2888+
self.on_dump(html).await?;
2889+
}
28862890
}
28872891
}
28882892
Ok(())

0 commit comments

Comments
 (0)