From 781a4484871b9bb03fce877f83b715bd8d783128 Mon Sep 17 00:00:00 2001 From: Suraj Gupta Date: Sat, 18 Jul 2026 00:54:28 -0400 Subject: [PATCH 1/3] Send PR url and repository host in InputContext.Git Adds url to PullRequest and host to Repository: - PullRequest.url: populated from PrInfo.url (gh pr view output) - Repository.host: populated by adding 'url' to the 'gh repo view' JSON fields and parsing the host from it Both fields are already available client-side; this change wires them through to the server so agents receive direct PR links. Bumps warp_multi_agent_api to proto PR commit (warpdotdev/warp-proto-apis#338). Co-Authored-By: Oz --- Cargo.toml | 3 ++- app/src/ai/agent/api/convert_to.rs | 5 ++++- app/src/ai/agent/api/convert_to_tests.rs | 9 +++++++++ app/src/ai/agent/mod.rs | 5 +++++ app/src/ai/blocklist/context_model.rs | 2 ++ app/src/ai/blocklist/context_model_tests.rs | 8 +++++++- .../github_repo_model/local_tests.rs | 1 + app/src/remote_server/git_status_proto.rs | 2 ++ app/src/util/git.rs | 10 +++++++++- app/src/util/git_tests.rs | 19 +++++++++++++++++++ crates/remote_server/proto/diff_state.proto | 2 ++ 11 files changed, 62 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 429f85e9b86..b17b7772505 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -342,7 +342,8 @@ version-compare = "0.1" vte = { git = "https://github.com/warpdotdev/vte.git", rev = "4b399c87b63ba88f45709edaa6383fc519f6c900", default-features = false } walkdir = "2" warp-workflows = { git = "https://github.com/warpdotdev/workflows", rev = "793a98ddda6ef19682aed66364faebd2829f0e01" } -warp_multi_agent_api = { git = "https://github.com/warpdotdev/warp-proto-apis.git", rev = "3d26f166b74469d5c12080ef6a3423fadee4d0e3" } +# TODO: restore stable rev once https://github.com/warpdotdev/warp-proto-apis/pull/338 is merged +warp_multi_agent_api = { git = "https://github.com/warpdotdev/warp-proto-apis.git", rev = "2ab8a60eddfb2cb06b149520d80556ce92b3b399" } wasm-bindgen = "0.2.89" wasm-bindgen-futures = "0.4.42" web-sys = { version = "0.3.69", features = [ diff --git a/app/src/ai/agent/api/convert_to.rs b/app/src/ai/agent/api/convert_to.rs index df4881fe602..a4277b63287 100644 --- a/app/src/ai/agent/api/convert_to.rs +++ b/app/src/ai/agent/api/convert_to.rs @@ -804,12 +804,13 @@ fn convert_context(context: &[AIAgentContext]) -> api::InputContext { api_git_context.head = head; api_git_context.branch = branch.unwrap_or_default(); } - AIAgentContext::Repository { name, owner } => { + AIAgentContext::Repository { name, owner, host } => { let api_git_context = git_context.get_or_insert_with(api::input_context::Git::default); api_git_context.repository = Some(api::input_context::git::Repository { name, owner: owner.unwrap_or_default(), + host: host.unwrap_or_default(), }); } AIAgentContext::PullRequest { @@ -817,6 +818,7 @@ fn convert_context(context: &[AIAgentContext]) -> api::InputContext { state, draft, base_branch, + url, } => { if number <= 0 { continue; @@ -828,6 +830,7 @@ fn convert_context(context: &[AIAgentContext]) -> api::InputContext { number, state: state as i32, base_branch, + url, }; let api_git_context = git_context.get_or_insert_with(api::input_context::Git::default); diff --git a/app/src/ai/agent/api/convert_to_tests.rs b/app/src/ai/agent/api/convert_to_tests.rs index f84eb88c43c..be6c3143915 100644 --- a/app/src/ai/agent/api/convert_to_tests.rs +++ b/app/src/ai/agent/api/convert_to_tests.rs @@ -19,12 +19,14 @@ fn git_context_converts_repository_and_pull_request_metadata() { AIAgentContext::Repository { name: "warp-internal".to_string(), owner: Some("warpdotdev".to_string()), + host: Some("github.com".to_string()), }, AIAgentContext::PullRequest { number: 42, state: "OPEN".to_string(), draft: true, base_branch: "main".to_string(), + url: "https://github.com/warpdotdev/warp-internal/pull/42".to_string(), }, ]; @@ -36,6 +38,7 @@ fn git_context_converts_repository_and_pull_request_metadata() { let repository = git.repository.expect("expected repository context"); assert_eq!(repository.name, "warp-internal"); assert_eq!(repository.owner, "warpdotdev"); + assert_eq!(repository.host, "github.com"); let pull_request = git.pull_request.expect("expected pull request context"); assert_eq!(pull_request.number, 42); @@ -44,6 +47,10 @@ fn git_context_converts_repository_and_pull_request_metadata() { api::input_context::git::pull_request::State::OpenDraft as i32 ); assert_eq!(pull_request.base_branch, "main"); + assert_eq!( + pull_request.url, + "https://github.com/warpdotdev/warp-internal/pull/42" + ); } #[test] @@ -59,6 +66,7 @@ fn git_context_skips_pull_request_metadata_with_invalid_number() { state: "OPEN".to_string(), draft: false, base_branch: "main".to_string(), + url: "https://github.com/warpdotdev/warp-internal/pull/1".to_string(), }, ]; @@ -82,6 +90,7 @@ fn git_context_skips_pull_request_metadata_with_unknown_state() { state: "SOMETHING_ELSE".to_string(), draft: false, base_branch: "main".to_string(), + url: "https://github.com/warpdotdev/warp-internal/pull/42".to_string(), }, ]; diff --git a/app/src/ai/agent/mod.rs b/app/src/ai/agent/mod.rs index c8e03ef2082..bba9e0fd839 100644 --- a/app/src/ai/agent/mod.rs +++ b/app/src/ai/agent/mod.rs @@ -2252,6 +2252,8 @@ pub enum AIAgentContext { name: String, /// The repository owner/organization (e.g. "warpdotdev"), if determinable from the remote URL. owner: Option, + /// The repository host (e.g. "github.com"), if determinable from the remote URL. + host: Option, }, /// Information about the GitHub pull request associated with the current branch. @@ -2268,6 +2270,9 @@ pub enum AIAgentContext { /// The pull request's base branch. #[serde(default)] base_branch: String, + /// The full URL of the pull request (e.g. "https://github.com/owner/repo/pull/123"). + #[serde(default)] + url: String, }, /// List of available skills is provided to the agent during initialization diff --git a/app/src/ai/blocklist/context_model.rs b/app/src/ai/blocklist/context_model.rs index 6a1c1fa4bf3..fa369160cad 100644 --- a/app/src/ai/blocklist/context_model.rs +++ b/app/src/ai/blocklist/context_model.rs @@ -832,6 +832,7 @@ impl BlocklistAIContextModel { AIAgentContext::Repository { name: repository_info.name.clone(), owner: repository_info.owner.clone(), + host: repository_info.host.clone(), } } @@ -846,6 +847,7 @@ impl BlocklistAIContextModel { state: pr_info.state.clone(), draft: pr_info.draft, base_branch: pr_info.base_branch.clone(), + url: pr_info.url.clone(), }) } diff --git a/app/src/ai/blocklist/context_model_tests.rs b/app/src/ai/blocklist/context_model_tests.rs index b0effd334cd..9a9e02cedea 100644 --- a/app/src/ai/blocklist/context_model_tests.rs +++ b/app/src/ai/blocklist/context_model_tests.rs @@ -190,6 +190,7 @@ fn repository_context_reads_github_repo_model() { Some(RepositoryInfo { name: "warp-internal".to_owned(), owner: Some("warpdotdev".to_owned()), + host: Some("github.com".to_owned()), }), ctx, ); @@ -205,6 +206,7 @@ fn repository_context_reads_github_repo_model() { Some(AIAgentContext::Repository { name: "warp-internal".to_owned(), owner: Some("warpdotdev".to_owned()), + host: Some("github.com".to_owned()), }) ); }); @@ -382,6 +384,7 @@ fn repository_context_from_repository_info_converts_to_agent_context() { let repository_info = RepositoryInfo { name: "warp-internal".to_owned(), owner: Some("warpdotdev".to_owned()), + host: Some("github.com".to_owned()), }; assert_eq!( @@ -389,12 +392,13 @@ fn repository_context_from_repository_info_converts_to_agent_context() { AIAgentContext::Repository { name: "warp-internal".to_owned(), owner: Some("warpdotdev".to_owned()), + host: Some("github.com".to_owned()), } ); } #[test] -fn pull_request_context_from_pr_info_excludes_url() { +fn pull_request_context_from_pr_info_includes_url() { let pr_info = PrInfo { number: 123, url: "https://github.com/warpdotdev/warp/pull/123".to_owned(), @@ -410,6 +414,7 @@ fn pull_request_context_from_pr_info_excludes_url() { state: "OPEN".to_owned(), draft: true, base_branch: "main".to_owned(), + url: "https://github.com/warpdotdev/warp/pull/123".to_owned(), }) ); } @@ -475,6 +480,7 @@ fn pull_request_context_reads_github_repo_model() { state: "OPEN".to_owned(), draft: false, base_branch: "main".to_owned(), + url: "https://github.com/warpdotdev/warp/pull/123".to_owned(), }) ); }); diff --git a/app/src/code_review/github_repo_model/local_tests.rs b/app/src/code_review/github_repo_model/local_tests.rs index d2c07c1f493..a01eb1cd135 100644 --- a/app/src/code_review/github_repo_model/local_tests.rs +++ b/app/src/code_review/github_repo_model/local_tests.rs @@ -20,6 +20,7 @@ fn repository_info() -> RepositoryInfo { RepositoryInfo { name: "warp".to_string(), owner: Some("warpdotdev".to_string()), + host: Some("github.com".to_string()), } } diff --git a/app/src/remote_server/git_status_proto.rs b/app/src/remote_server/git_status_proto.rs index e1745eeb8c0..093425b4b78 100644 --- a/app/src/remote_server/git_status_proto.rs +++ b/app/src/remote_server/git_status_proto.rs @@ -18,6 +18,7 @@ impl From<&proto::RepositoryInfo> for RepositoryInfo { RepositoryInfo { name: info.name.clone(), owner: info.owner.clone(), + host: info.host.clone(), } } } @@ -27,6 +28,7 @@ impl From<&RepositoryInfo> for proto::RepositoryInfo { proto::RepositoryInfo { name: info.name.clone(), owner: info.owner.clone(), + host: info.host.clone(), } } } diff --git a/app/src/util/git.rs b/app/src/util/git.rs index ed9a293eaae..9029ddf7f2b 100644 --- a/app/src/util/git.rs +++ b/app/src/util/git.rs @@ -737,6 +737,8 @@ pub struct PrInfo { pub struct RepositoryInfo { pub name: String, pub owner: Option, + /// The repository host (e.g. "github.com"), parsed from the repo URL. + pub host: Option, } #[cfg(feature = "local_fs")] @@ -753,9 +755,15 @@ fn repository_info_from_gh_output(output: &str) -> Result { .filter(|owner| !owner.is_empty()) .ok_or_else(|| anyhow!("Missing 'owner.login' in gh output"))? .to_string(); + // The host is best-effort: parsed from the repo URL when present. + let host = parsed["url"] + .as_str() + .and_then(|u| url::Url::parse(u).ok()) + .and_then(|u| u.host_str().map(|host| host.to_string())); Ok(RepositoryInfo { name, owner: Some(owner), + host, }) } @@ -773,7 +781,7 @@ pub async fn get_repository_info( match run_gh_command( repo_path, - &["repo", "view", "--json", "name,owner"], + &["repo", "view", "--json", "name,owner,url"], path_env, ) .await diff --git a/app/src/util/git_tests.rs b/app/src/util/git_tests.rs index 4d998a921ee..072c7e0e6d4 100644 --- a/app/src/util/git_tests.rs +++ b/app/src/util/git_tests.rs @@ -25,6 +25,7 @@ async fn git(repo: &Path, args: &[&str]) -> String { #[cfg(feature = "local_fs")] #[test] fn repository_info_from_gh_output_parses_name_and_owner() { + // No url in the output => host is absent. assert_eq!( super::repository_info_from_gh_output( r#"{"name":"warp-internal","owner":{"login":"warpdotdev"}}"# @@ -33,6 +34,23 @@ fn repository_info_from_gh_output_parses_name_and_owner() { RepositoryInfo { name: "warp-internal".to_owned(), owner: Some("warpdotdev".to_owned()), + host: None, + } + ); +} + +#[cfg(feature = "local_fs")] +#[test] +fn repository_info_from_gh_output_parses_host_from_url() { + assert_eq!( + super::repository_info_from_gh_output( + r#"{"name":"warp-internal","owner":{"login":"warpdotdev"},"url":"https://github.com/warpdotdev/warp-internal"}"# + ) + .unwrap(), + RepositoryInfo { + name: "warp-internal".to_owned(), + owner: Some("warpdotdev".to_owned()), + host: Some("github.com".to_owned()), } ); } @@ -146,6 +164,7 @@ async fn get_repository_info_reads_gh_repo_view() { Some(RepositoryInfo { name: "warp-internal".to_owned(), owner: Some("warpdotdev".to_owned()), + host: None, }) ); } diff --git a/crates/remote_server/proto/diff_state.proto b/crates/remote_server/proto/diff_state.proto index d4a7863824a..42b69b17a22 100644 --- a/crates/remote_server/proto/diff_state.proto +++ b/crates/remote_server/proto/diff_state.proto @@ -455,6 +455,8 @@ message GitGenerateCommitMessageResponse { message RepositoryInfo { string name = 1; optional string owner = 2; + // The repository host (e.g. "github.com"), parsed from the repo URL. + optional string host = 3; } // Mirrors GitStatusMetadata in Rust (code_review/git_repo_model/mod.rs). From c4fdf5be924b032fbe1a6281b518e6e0b18a144a Mon Sep 17 00:00:00 2001 From: Suraj Gupta Date: Sat, 18 Jul 2026 01:03:00 -0400 Subject: [PATCH 2/3] Update Cargo.lock for new proto rev Co-Authored-By: Oz --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index cb508c5dc83..2c9d71ed98b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15735,7 +15735,7 @@ dependencies = [ [[package]] name = "warp_multi_agent_api" version = "0.0.0" -source = "git+https://github.com/warpdotdev/warp-proto-apis.git?rev=3d26f166b74469d5c12080ef6a3423fadee4d0e3#3d26f166b74469d5c12080ef6a3423fadee4d0e3" +source = "git+https://github.com/warpdotdev/warp-proto-apis.git?rev=2ab8a60eddfb2cb06b149520d80556ce92b3b399#2ab8a60eddfb2cb06b149520d80556ce92b3b399" dependencies = [ "prost", "prost-reflect", From e95a9f9db2e1e6ec85b2237bef507d7f749b02b3 Mon Sep 17 00:00:00 2001 From: Suraj Gupta Date: Sat, 18 Jul 2026 14:50:40 -0400 Subject: [PATCH 3/3] ref --- Cargo.lock | 2 +- Cargo.toml | 3 +-- app/src/ai/agent/conversation_tests.rs | 1 + crates/ai/src/agent/action/convert_tests.rs | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c9d71ed98b..5d9a35dea99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15735,7 +15735,7 @@ dependencies = [ [[package]] name = "warp_multi_agent_api" version = "0.0.0" -source = "git+https://github.com/warpdotdev/warp-proto-apis.git?rev=2ab8a60eddfb2cb06b149520d80556ce92b3b399#2ab8a60eddfb2cb06b149520d80556ce92b3b399" +source = "git+https://github.com/warpdotdev/warp-proto-apis.git?rev=4f566de018be84b36d1dc9a86c6916ec2a9b7823#4f566de018be84b36d1dc9a86c6916ec2a9b7823" dependencies = [ "prost", "prost-reflect", diff --git a/Cargo.toml b/Cargo.toml index b17b7772505..382f634269c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -342,8 +342,7 @@ version-compare = "0.1" vte = { git = "https://github.com/warpdotdev/vte.git", rev = "4b399c87b63ba88f45709edaa6383fc519f6c900", default-features = false } walkdir = "2" warp-workflows = { git = "https://github.com/warpdotdev/workflows", rev = "793a98ddda6ef19682aed66364faebd2829f0e01" } -# TODO: restore stable rev once https://github.com/warpdotdev/warp-proto-apis/pull/338 is merged -warp_multi_agent_api = { git = "https://github.com/warpdotdev/warp-proto-apis.git", rev = "2ab8a60eddfb2cb06b149520d80556ce92b3b399" } +warp_multi_agent_api = { git = "https://github.com/warpdotdev/warp-proto-apis.git", rev = "4f566de018be84b36d1dc9a86c6916ec2a9b7823" } wasm-bindgen = "0.2.89" wasm-bindgen-futures = "0.4.42" web-sys = { version = "0.3.69", features = [ diff --git a/app/src/ai/agent/conversation_tests.rs b/app/src/ai/agent/conversation_tests.rs index b8624d6381a..4baaf9827ca 100644 --- a/app/src/ai/agent/conversation_tests.rs +++ b/app/src/ai/agent/conversation_tests.rs @@ -123,6 +123,7 @@ fn start_recording_tool_call() -> api::message::tool_call::Tool { summary: String::new(), playback_speed_multiplier: 0, target: None, + description: String::new(), }) } diff --git a/crates/ai/src/agent/action/convert_tests.rs b/crates/ai/src/agent/action/convert_tests.rs index 83504196888..23c17d784c2 100644 --- a/crates/ai/src/agent/action/convert_tests.rs +++ b/crates/ai/src/agent/action/convert_tests.rs @@ -13,6 +13,7 @@ fn start_recording( summary: String::new(), playback_speed_multiplier: 0, target, + description: String::new(), } }