Skip to content

Commit f839b41

Browse files
committed
feat(compression): handle minimal unified diffs
1 parent a99be2e commit f839b41

4 files changed

Lines changed: 169 additions & 11 deletions

File tree

lib/codex_pooler/gateway/request_compression/content_detector.ex

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,26 @@ defmodule CodexPooler.Gateway.RequestCompression.ContentDetector do
125125
end
126126

127127
defp diff_points(content) do
128+
hunks = scan_count(@diff_hunk_regex, content)
129+
additions = scan_count(@diff_addition_regex, content)
130+
deletions = scan_count(@diff_deletion_regex, content)
131+
changes = additions + deletions
132+
133+
if hunks == 0 or changes == 0 do
134+
0
135+
else
136+
diff_points(content, hunks, additions, deletions, changes)
137+
end
138+
end
139+
140+
defp diff_points(content, hunks, additions, deletions, changes) do
128141
[
129-
score(regex_match?(@diff_git_regex, content), 25),
130-
score(regex_match?(@diff_index_regex, content), 10),
131-
score(regex_match?(@diff_file_header_regex, content), 25),
132-
score(regex_match?(@diff_hunk_regex, content), 25),
133-
score(
134-
regex_match?(@diff_addition_regex, content) and
135-
regex_match?(@diff_deletion_regex, content),
136-
20
137-
)
142+
cond_score(hunks, [{2, 50}, {1, 45}]),
143+
cond_score(changes, [{2, 30}, {1, 25}]),
144+
score(additions > 0 and deletions > 0, 10),
145+
score(regex_match?(@diff_file_header_regex, content), 15),
146+
score(regex_match?(@diff_git_regex, content), 10),
147+
score(regex_match?(@diff_index_regex, content), 5)
138148
]
139149
|> Enum.sum()
140150
|> min(100)

lib/codex_pooler/gateway/request_compression/strategies/diff.ex

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ defmodule CodexPooler.Gateway.RequestCompression.Strategies.Diff do
1010
@default_max_hunks 32
1111
@default_max_hunks_per_file 8
1212
@default_context_lines 2
13+
@default_model "gpt-4o"
1314

1415
@file_start_regex ~r/^diff --git\s+\S+\s+\S+/
1516
@hunk_header_regex ~r/^@@\s+-\d+(?:,\d+)?\s+\+\d+(?:,\d+)?\s+@@/
@@ -36,7 +37,7 @@ defmodule CodexPooler.Gateway.RequestCompression.Strategies.Diff do
3637

3738
compressed = Strategies.join_lines(compressed_lines)
3839

39-
Strategies.finalize(
40+
finalize(
4041
@strategy,
4142
content,
4243
compressed,
@@ -198,7 +199,14 @@ defmodule CodexPooler.Gateway.RequestCompression.Strategies.Diff do
198199
not changed_line?(line) and index in selected_indexes
199200
end)
200201

201-
{[hunk.header | body_lines], kept_context_line_count, omitted_context_line_count}
202+
hunk_lines =
203+
if kept_context_line_count == 0 and omitted_context_line_count == 0 do
204+
body_lines
205+
else
206+
[hunk.header | body_lines]
207+
end
208+
209+
{hunk_lines, kept_context_line_count, omitted_context_line_count}
202210
end
203211

204212
defp selected_hunk_indexes(lines, context_lines) do
@@ -281,4 +289,20 @@ defmodule CodexPooler.Gateway.RequestCompression.Strategies.Diff do
281289
(String.starts_with?(line, "+") and not String.starts_with?(line, "+++")) or
282290
(String.starts_with?(line, "-") and not String.starts_with?(line, "---"))
283291
end
292+
293+
defp finalize(strategy, original, compressed, counts, opts) do
294+
Strategies.finalize(strategy, original, compressed, counts, default_model_opts(opts))
295+
end
296+
297+
defp default_model_opts(opts) when is_list(opts) do
298+
if Keyword.has_key?(opts, :model), do: opts, else: Keyword.put(opts, :model, @default_model)
299+
end
300+
301+
defp default_model_opts(opts) when is_map(opts) do
302+
if Map.has_key?(opts, :model) or Map.has_key?(opts, "model") do
303+
opts
304+
else
305+
Map.put(opts, :model, @default_model)
306+
end
307+
end
284308
end

test/codex_pooler/gateway/request_compression/content_detector_test.exs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,53 @@ defmodule CodexPooler.Gateway.RequestCompression.ContentDetectorTest do
100100
assert_noop(:text, ContentDetector.detect(negative))
101101
end
102102

103+
test "detects one-sided and minimal unified hunks without diff git headers" do
104+
additions_only = """
105+
@@ -1,0 +1,3 @@
106+
+added one
107+
+added two
108+
+added three
109+
"""
110+
111+
deletions_only = """
112+
@@ -1,3 +1,0 @@
113+
-removed one
114+
-removed two
115+
-removed three
116+
"""
117+
118+
replacement = """
119+
@@ -1 +1 @@
120+
-old value
121+
+new value
122+
"""
123+
124+
for body <- [additions_only, deletions_only, replacement] do
125+
assert %{kind: :diff, confidence: confidence, compressible: true, strategy: :diff} =
126+
ContentDetector.detect(body)
127+
128+
assert confidence >= 0.7
129+
end
130+
end
131+
132+
test "keeps prose with isolated plus and minus lines out of diff detection" do
133+
prose = """
134+
Review notes for the next change:
135+
+ add a short summary before the examples
136+
- remove the stale paragraph near the end
137+
"""
138+
139+
missing_hunk = """
140+
--- a/example.txt
141+
+++ b/example.txt
142+
+added without a hunk header
143+
-removed without a hunk header
144+
"""
145+
146+
assert_noop(:text, ContentDetector.detect(prose))
147+
assert_noop(:text, ContentDetector.detect(missing_hunk))
148+
end
149+
103150
test "detects HTML at request-compression confidence but keeps it no-op" do
104151
positive = """
105152
<!doctype html>

test/codex_pooler/gateway/request_compression/strategies/diff_test.exs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,56 @@ defmodule CodexPooler.Gateway.RequestCompression.Strategies.DiffTest do
9999
assert :skip = Diff.compress("ordinary output without diff hunks", min_bytes: 0)
100100
end
101101

102+
test "compresses one-sided and replacement hunks without diff git headers" do
103+
for {content, expected_additions, expected_deletions} <- [
104+
{additions_only_fixture(), 1, 0},
105+
{deletions_only_fixture(), 0, 1},
106+
{replacement_fixture(), 1, 1}
107+
] do
108+
assert {:ok, %{content: compressed, metadata: metadata}} =
109+
Diff.compress(content,
110+
model: @model,
111+
min_bytes: 0,
112+
min_hunks: 1,
113+
context_lines: 0
114+
)
115+
116+
assert compressed =~ "@@"
117+
assert compressed =~ "[compressed diff output: omitted"
118+
assert metadata.strategy == :diff
119+
assert metadata.original_hunk_count == 1
120+
assert metadata.compressed_hunk_count == 1
121+
assert metadata.addition_line_count == expected_additions
122+
assert metadata.deletion_line_count == expected_deletions
123+
end
124+
end
125+
126+
test "compresses minimal bare unified hunks and skips prose plus/minus lines" do
127+
minimal = """
128+
@@ -1,0 +1,2 @@
129+
+one
130+
+two
131+
"""
132+
133+
prose = """
134+
Review notes for the next change:
135+
+ add a short summary before the examples
136+
- remove the stale paragraph near the end
137+
"""
138+
139+
assert {:ok, %{content: compressed, metadata: metadata}} =
140+
Diff.compress(minimal, min_bytes: 0, min_hunks: 1)
141+
142+
assert compressed == "+one\n+two"
143+
assert metadata.strategy == :diff
144+
assert metadata.original_hunk_count == 1
145+
assert metadata.compressed_hunk_count == 1
146+
assert metadata.addition_line_count == 2
147+
assert metadata.deletion_line_count == 0
148+
149+
assert :skip = Diff.compress(prose, min_bytes: 0, min_hunks: 1)
150+
end
151+
102152
test "does not retain state between calls" do
103153
assert {:ok, _result} =
104154
Diff.compress(diff_fixture("DROP_ME_STALE_DIFF"),
@@ -151,6 +201,33 @@ defmodule CodexPooler.Gateway.RequestCompression.Strategies.DiffTest do
151201
"""
152202
end
153203

204+
defp additions_only_fixture do
205+
diff_with_context("+added value")
206+
end
207+
208+
defp deletions_only_fixture do
209+
diff_with_context("-removed value")
210+
end
211+
212+
defp replacement_fixture do
213+
diff_with_context("""
214+
-old value
215+
+new value
216+
""")
217+
end
218+
219+
defp diff_with_context(change) do
220+
context =
221+
1..24
222+
|> Enum.map_join("\n", &" context line #{&1}")
223+
224+
"""
225+
@@ -1,25 +1,25 @@
226+
#{context}
227+
#{String.trim_trailing(change)}
228+
"""
229+
end
230+
154231
defp token_count(content) do
155232
assert {:ok, count, _metadata} = TokenCounter.count(@model, content)
156233
count

0 commit comments

Comments
 (0)