From 61b7a1a93b55ced152930c2341b90f2d1e91f224 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Fri, 24 Apr 2026 05:59:54 +0530 Subject: [PATCH 1/4] feat(provider): add gpt-5.5 model to OpenAI provider --- crates/forge_repo/src/provider/provider.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/forge_repo/src/provider/provider.json b/crates/forge_repo/src/provider/provider.json index 1343680862..ccca0a6b02 100644 --- a/crates/forge_repo/src/provider/provider.json +++ b/crates/forge_repo/src/provider/provider.json @@ -2406,6 +2406,16 @@ "response_type": "OpenAI", "url": "https://chatgpt.com/backend-api/codex/responses", "models": [ + { + "id": "gpt-5.5", + "name": "GPT-5.5", + "description": "Latest frontier model for complex professional work", + "context_length": 400000, + "tools_supported": true, + "supports_parallel_tool_calls": true, + "supports_reasoning": true, + "input_modalities": ["text", "image"] + }, { "id": "gpt-5.4-mini", "name": "GPT-5.4 Mini", From 4ade64453082f8492c79e42ab661c0c7c4d63ddc Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Fri, 24 Apr 2026 06:07:12 +0530 Subject: [PATCH 2/4] chore(deps): switch posthog-rs from git rev to published crate v0.5.3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 454061fe53..dd01ce8b3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ machineid-rs = "1.2.4" mockito = "1.7.2" nom = "8.0.0" nu-ansi-term = "0.50.1" -posthog-rs = { git = "https://github.com/PostHog/posthog-rs.git", rev = "83cf3d2c3b6edab8600276d06b76c73e59af8ebd" } +posthog-rs = "0.5.3" pretty_assertions = "1.4.1" proc-macro2 = "1.0" quote = "1.0" From e37ec3cedf8dd4d38b2f48ebb58d4af8c096b802 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Fri, 24 Apr 2026 08:32:37 +0530 Subject: [PATCH 3/4] feat(patch): add text patch repository and gRPC service for fuzzy replacement --- crates/forge_domain/src/repo.rs | 16 ++++++ crates/forge_repo/proto/forge.proto | 14 ++++++ crates/forge_repo/src/forge_repo.rs | 24 ++++++++- crates/forge_services/src/forge_services.rs | 4 +- .../src/tool_services/fs_patch.rs | 50 +++++++++++-------- 5 files changed, 85 insertions(+), 23 deletions(-) diff --git a/crates/forge_domain/src/repo.rs b/crates/forge_domain/src/repo.rs index 4d6205f575..558d54244a 100644 --- a/crates/forge_domain/src/repo.rs +++ b/crates/forge_domain/src/repo.rs @@ -9,6 +9,12 @@ use crate::{ SearchMatch, Skill, Snapshot, WorkspaceAuth, WorkspaceId, }; +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TextPatchBlock { + pub patch: String, + pub patched_text: String, +} + /// Repository for managing file snapshots /// /// This repository provides operations for creating and restoring file @@ -232,3 +238,13 @@ pub trait FuzzySearchRepository: Send + Sync { search_all: bool, ) -> Result>; } + +#[async_trait::async_trait] +pub trait TextPatchRepository: Send + Sync { + async fn build_text_patch( + &self, + haystack: &str, + old_string: &str, + new_string: &str, + ) -> Result; +} diff --git a/crates/forge_repo/proto/forge.proto b/crates/forge_repo/proto/forge.proto index 5ea339a85d..e02a3e1c6a 100644 --- a/crates/forge_repo/proto/forge.proto +++ b/crates/forge_repo/proto/forge.proto @@ -47,6 +47,9 @@ service ForgeService { // Searches for needle in haystack using fuzzy search rpc FuzzySearch(FuzzySearchRequest) returns (FuzzySearchResponse); + + // Builds a serialized text patch for fuzzy replacement + rpc BuildTextPatch(BuildTextPatchRequest) returns (BuildTextPatchResponse); } // Node types @@ -356,6 +359,17 @@ message FuzzySearchResponse { repeated SearchMatch matches = 1; } +message BuildTextPatchRequest { + string haystack = 1; + string old_string = 2; + string new_string = 3; +} + +message BuildTextPatchResponse { + string patch = 1; + string patched_text = 2; +} + message SearchMatch { uint32 start_line = 1; uint32 end_line = 2; diff --git a/crates/forge_repo/src/forge_repo.rs b/crates/forge_repo/src/forge_repo.rs index 34d1bb8498..b959907ee3 100644 --- a/crates/forge_repo/src/forge_repo.rs +++ b/crates/forge_repo/src/forge_repo.rs @@ -14,7 +14,7 @@ use forge_domain::{ Conversation, ConversationId, ConversationRepository, Environment, FileInfo, FuzzySearchRepository, McpServerConfig, MigrationResult, Model, ModelId, Provider, ProviderId, ProviderRepository, ResultStream, SearchMatch, Skill, SkillRepository, Snapshot, - SnapshotRepository, + SnapshotRepository, TextPatchBlock, TextPatchRepository, }; // Re-export CacacheStorage from forge_infra pub use forge_infra::CacacheStorage; @@ -628,6 +628,28 @@ impl FuzzySearchRepository for ForgeRepo { } } +#[async_trait::async_trait] +impl TextPatchRepository for ForgeRepo { + async fn build_text_patch( + &self, + haystack: &str, + old_string: &str, + new_string: &str, + ) -> anyhow::Result { + let request = tonic::Request::new(crate::proto_generated::BuildTextPatchRequest { + haystack: haystack.to_string(), + old_string: old_string.to_string(), + new_string: new_string.to_string(), + }); + + let channel = self.infra.channel()?; + let mut client = crate::proto_generated::forge_service_client::ForgeServiceClient::new(channel); + let response = client.build_text_patch(request).await?.into_inner(); + + Ok(TextPatchBlock { patch: response.patch, patched_text: response.patched_text }) + } +} + impl GrpcInfra for ForgeRepo { fn channel(&self) -> anyhow::Result { self.infra.channel() diff --git a/crates/forge_services/src/forge_services.rs b/crates/forge_services/src/forge_services.rs index 7ff1d1a2fb..cd2f775899 100644 --- a/crates/forge_services/src/forge_services.rs +++ b/crates/forge_services/src/forge_services.rs @@ -7,7 +7,8 @@ use forge_app::{ }; use forge_domain::{ ChatRepository, ConversationRepository, FuzzySearchRepository, ProviderRepository, - SkillRepository, SnapshotRepository, ValidationRepository, WorkspaceIndexRepository, + SkillRepository, SnapshotRepository, TextPatchRepository, ValidationRepository, + WorkspaceIndexRepository, }; use crate::ForgeProviderAuthService; @@ -199,6 +200,7 @@ impl< + WorkspaceIndexRepository + ValidationRepository + FuzzySearchRepository + + TextPatchRepository + Clone + 'static, > Services for ForgeServices diff --git a/crates/forge_services/src/tool_services/fs_patch.rs b/crates/forge_services/src/tool_services/fs_patch.rs index b5c674fd69..0e3b8d092c 100644 --- a/crates/forge_services/src/tool_services/fs_patch.rs +++ b/crates/forge_services/src/tool_services/fs_patch.rs @@ -4,7 +4,10 @@ use std::sync::Arc; use bytes::Bytes; use forge_app::domain::PatchOperation; use forge_app::{FileWriterInfra, FsPatchService, PatchOutput, compute_hash}; -use forge_domain::{FuzzySearchRepository, SearchMatch, SnapshotRepository, ValidationRepository}; +use forge_domain::{ + FuzzySearchRepository, SearchMatch, SnapshotRepository, TextPatchRepository, + ValidationRepository, +}; use thiserror::Error; use tokio::fs; @@ -145,6 +148,8 @@ enum Error { "Match range [{0}..{1}) is out of bounds for content of length {2}. File may have changed externally, consider reading the file again." )] RangeOutOfBounds(usize, usize, usize), + #[error("Failed to build fuzzy patch: {message}")] + PatchBuild { message: String }, } /// Compute a range from search text, with operation-aware error handling @@ -371,8 +376,13 @@ impl ForgeFsPatch { } #[async_trait::async_trait] -impl - FsPatchService for ForgeFsPatch +impl< + F: FileWriterInfra + + SnapshotRepository + + ValidationRepository + + FuzzySearchRepository + + TextPatchRepository, + > FsPatchService for ForgeFsPatch { async fn patch( &self, @@ -400,36 +410,34 @@ impl r, + current_content = match compute_range(¤t_content, Some(&search), &operation) { + Ok(range) => apply_replacement(current_content, range, &operation, &content)?, Err(Error::NoMatch(search_text)) if matches!( operation, PatchOperation::Replace | PatchOperation::ReplaceAll | PatchOperation::Swap ) => { - // Try fuzzy search as fallback - match self + let normalized_search = + Range::normalize_search_line_endings(¤t_content, &search_text); + let normalized_content = + Range::normalize_search_line_endings(¤t_content, &content); + let patch = self .infra - .fuzzy_search(&search_text, ¤t_content, false) + .build_text_patch( + ¤t_content, + &normalized_search, + &normalized_content, + ) .await - { - Ok(matches) if !matches.is_empty() => { - // Use the first fuzzy match - matches - .first() - .map(|m| Range::from_search_match(¤t_content, m)) - } - _ => return Err(Error::NoMatch(search_text).into()), - } + .map_err(|error| Error::PatchBuild { + message: error.to_string(), + })?; + patch.patched_text } Err(e) => return Err(e.into()), }; - // Apply the replacement - current_content = apply_replacement(current_content, range, &operation, &content)?; - // SNAPSHOT COORDINATION: Always capture snapshot before modifying self.infra.insert_snapshot(path).await?; From 925b2bdf8dbbe948c695666a26d6fc4126a7745e Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 03:09:10 +0000 Subject: [PATCH 4/4] [autofix.ci] apply automated fixes --- crates/forge_repo/src/forge_repo.rs | 3 ++- .../src/tool_services/fs_patch.rs | 22 +++++++------------ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/crates/forge_repo/src/forge_repo.rs b/crates/forge_repo/src/forge_repo.rs index b959907ee3..3d390cee29 100644 --- a/crates/forge_repo/src/forge_repo.rs +++ b/crates/forge_repo/src/forge_repo.rs @@ -643,7 +643,8 @@ impl TextPatchRepository for ForgeRepo { }); let channel = self.infra.channel()?; - let mut client = crate::proto_generated::forge_service_client::ForgeServiceClient::new(channel); + let mut client = + crate::proto_generated::forge_service_client::ForgeServiceClient::new(channel); let response = client.build_text_patch(request).await?.into_inner(); Ok(TextPatchBlock { patch: response.patch, patched_text: response.patched_text }) diff --git a/crates/forge_services/src/tool_services/fs_patch.rs b/crates/forge_services/src/tool_services/fs_patch.rs index 0e3b8d092c..476b2aa961 100644 --- a/crates/forge_services/src/tool_services/fs_patch.rs +++ b/crates/forge_services/src/tool_services/fs_patch.rs @@ -377,12 +377,12 @@ impl ForgeFsPatch { #[async_trait::async_trait] impl< - F: FileWriterInfra - + SnapshotRepository - + ValidationRepository - + FuzzySearchRepository - + TextPatchRepository, - > FsPatchService for ForgeFsPatch + F: FileWriterInfra + + SnapshotRepository + + ValidationRepository + + FuzzySearchRepository + + TextPatchRepository, +> FsPatchService for ForgeFsPatch { async fn patch( &self, @@ -424,15 +424,9 @@ impl< Range::normalize_search_line_endings(¤t_content, &content); let patch = self .infra - .build_text_patch( - ¤t_content, - &normalized_search, - &normalized_content, - ) + .build_text_patch(¤t_content, &normalized_search, &normalized_content) .await - .map_err(|error| Error::PatchBuild { - message: error.to_string(), - })?; + .map_err(|error| Error::PatchBuild { message: error.to_string() })?; patch.patched_text } Err(e) => return Err(e.into()),