gptdiff 0.1.7__tar.gz → 0.1.9__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {gptdiff-0.1.7 → gptdiff-0.1.9}/PKG-INFO +2 -2
- {gptdiff-0.1.7 → gptdiff-0.1.9}/gptdiff/gptdiff.py +50 -8
- {gptdiff-0.1.7 → gptdiff-0.1.9}/gptdiff.egg-info/PKG-INFO +2 -2
- {gptdiff-0.1.7 → gptdiff-0.1.9}/gptdiff.egg-info/SOURCES.txt +1 -0
- {gptdiff-0.1.7 → gptdiff-0.1.9}/gptdiff.egg-info/requires.txt +1 -1
- {gptdiff-0.1.7 → gptdiff-0.1.9}/setup.py +2 -2
- gptdiff-0.1.9/tests/test_diff_parse.py +35 -0
- {gptdiff-0.1.7 → gptdiff-0.1.9}/tests/test_smartapply.py +1 -1
- {gptdiff-0.1.7 → gptdiff-0.1.9}/LICENSE.txt +0 -0
- {gptdiff-0.1.7 → gptdiff-0.1.9}/README.md +0 -0
- {gptdiff-0.1.7 → gptdiff-0.1.9}/gptdiff/__init__.py +0 -0
- {gptdiff-0.1.7 → gptdiff-0.1.9}/gptdiff.egg-info/dependency_links.txt +0 -0
- {gptdiff-0.1.7 → gptdiff-0.1.9}/gptdiff.egg-info/entry_points.txt +0 -0
- {gptdiff-0.1.7 → gptdiff-0.1.9}/gptdiff.egg-info/top_level.txt +0 -0
- {gptdiff-0.1.7 → gptdiff-0.1.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: gptdiff
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.9
|
4
4
|
Summary: A tool to generate and apply git diffs using LLMs
|
5
5
|
Author: 255labs
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
@@ -10,7 +10,7 @@ Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE.txt
|
11
11
|
Requires-Dist: openai>=1.0.0
|
12
12
|
Requires-Dist: tiktoken>=0.5.0
|
13
|
-
Requires-Dist: ai_agent_toolbox>=0.1.
|
13
|
+
Requires-Dist: ai_agent_toolbox>=0.1.11
|
14
14
|
Provides-Extra: test
|
15
15
|
Requires-Dist: pytest; extra == "test"
|
16
16
|
Requires-Dist: pytest-mock; extra == "test"
|
@@ -67,6 +67,25 @@ def create_think_toolbox():
|
|
67
67
|
)
|
68
68
|
return toolbox
|
69
69
|
|
70
|
+
def color_code_diff(diff_text: str) -> str:
|
71
|
+
"""
|
72
|
+
Color code lines in a diff. Lines beginning with '-' in red, and
|
73
|
+
lines beginning with '+' in green.
|
74
|
+
"""
|
75
|
+
red = "\033[31m"
|
76
|
+
green = "\033[32m"
|
77
|
+
reset = "\033[0m"
|
78
|
+
|
79
|
+
colorized_lines = []
|
80
|
+
for line in diff_text.split('\n'):
|
81
|
+
if line.startswith('-'):
|
82
|
+
colorized_lines.append(f"{red}{line}{reset}")
|
83
|
+
elif line.startswith('+'):
|
84
|
+
colorized_lines.append(f"{green}{line}{reset}")
|
85
|
+
else:
|
86
|
+
colorized_lines.append(line)
|
87
|
+
|
88
|
+
return '\n'.join(colorized_lines)
|
70
89
|
|
71
90
|
def load_gitignore_patterns(gitignore_path):
|
72
91
|
with open(gitignore_path, 'r') as f:
|
@@ -308,8 +327,16 @@ def smartapply(diff_text, files, model=None, api_key=None, base_url=None):
|
|
308
327
|
updated = call_llm_for_apply_with_think_tool_available(path, original, patch, model, api_key=api_key, base_url=base_url)
|
309
328
|
files[path] = updated.strip()
|
310
329
|
|
330
|
+
threads = []
|
331
|
+
|
311
332
|
for path, patch in parsed_diffs:
|
312
|
-
process_file(path, patch)
|
333
|
+
thread = threading.Thread(target=process_file, args=(path, patch))
|
334
|
+
thread.start()
|
335
|
+
threads.append(thread)
|
336
|
+
|
337
|
+
# Wait for all threads to complete
|
338
|
+
for thread in threads:
|
339
|
+
thread.join()
|
313
340
|
|
314
341
|
return files
|
315
342
|
|
@@ -339,6 +366,7 @@ def parse_arguments():
|
|
339
366
|
parser.add_argument('--temperature', type=float, default=0.7, help='Temperature parameter for model creativity (0.0 to 2.0)')
|
340
367
|
parser.add_argument('--max_tokens', type=int, default=30000, help='Temperature parameter for model creativity (0.0 to 2.0)')
|
341
368
|
parser.add_argument('--model', type=str, default=None, help='Model to use for the API call.')
|
369
|
+
parser.add_argument('--applymodel', type=str, default=None, help='Model to use for applying the diff. Defaults to the value of --model if not specified.')
|
342
370
|
|
343
371
|
parser.add_argument('--nowarn', action='store_true', help='Disable large token warning')
|
344
372
|
|
@@ -404,11 +432,11 @@ def parse_diff_per_file(diff_text):
|
|
404
432
|
|
405
433
|
return diffs
|
406
434
|
|
407
|
-
def call_llm_for_apply_with_think_tool_available(file_path, original_content, file_diff, model, api_key=None, base_url=None):
|
435
|
+
def call_llm_for_apply_with_think_tool_available(file_path, original_content, file_diff, model, api_key=None, base_url=None, extra_prompt=None, max_tokens=30000):
|
408
436
|
parser = FlatXMLParser("think")
|
409
437
|
formatter = FlatXMLPromptFormatter(tag="think")
|
410
438
|
toolbox = create_think_toolbox()
|
411
|
-
full_response = call_llm_for_apply(file_path, original_content, file_diff, model, api_key=
|
439
|
+
full_response = call_llm_for_apply(file_path, original_content, file_diff, model, api_key=api_key, base_url=base_url, extra_prompt=extra_prompt, max_tokens=max_tokens)
|
412
440
|
notool_response = ""
|
413
441
|
events = parser.parse(full_response)
|
414
442
|
is_in_tool = False
|
@@ -424,7 +452,7 @@ def call_llm_for_apply_with_think_tool_available(file_path, original_content, fi
|
|
424
452
|
|
425
453
|
return notool_response
|
426
454
|
|
427
|
-
def call_llm_for_apply(file_path, original_content, file_diff, model, api_key=None, base_url=None):
|
455
|
+
def call_llm_for_apply(file_path, original_content, file_diff, model, api_key=None, base_url=None, extra_prompt=None, max_tokens=30000):
|
428
456
|
"""AI-powered diff application with conflict resolution.
|
429
457
|
|
430
458
|
Internal workhorse for smartapply that handles individual file patches.
|
@@ -474,6 +502,8 @@ Diff to apply:
|
|
474
502
|
{file_diff}
|
475
503
|
</diff>"""
|
476
504
|
|
505
|
+
if extra_prompt:
|
506
|
+
user_prompt += f"\n\n{extra_prompt}"
|
477
507
|
if model == "gemini-2.0-flash-thinking-exp-01-21":
|
478
508
|
user_prompt = system_prompt+"\n"+user_prompt
|
479
509
|
messages = [
|
@@ -490,7 +520,7 @@ Diff to apply:
|
|
490
520
|
response = client.chat.completions.create(model=model,
|
491
521
|
messages=messages,
|
492
522
|
temperature=0.0,
|
493
|
-
max_tokens=
|
523
|
+
max_tokens=max_tokens)
|
494
524
|
full_response = response.choices[0].message.content
|
495
525
|
|
496
526
|
elapsed = time.time() - start_time
|
@@ -590,12 +620,21 @@ def main():
|
|
590
620
|
if confirmation != 'y':
|
591
621
|
print("Request canceled")
|
592
622
|
sys.exit(0)
|
593
|
-
|
623
|
+
try:
|
624
|
+
full_text, diff_text, prompt_tokens, completion_tokens, total_tokens, cost = call_llm_for_diff(system_prompt, user_prompt, files_content, args.model,
|
594
625
|
temperature=args.temperature,
|
595
626
|
api_key=os.getenv('GPTDIFF_LLM_API_KEY'),
|
596
627
|
base_url=os.getenv('GPTDIFF_LLM_BASE_URL', "https://nano-gpt.com/api/v1/"),
|
597
628
|
max_tokens=args.max_tokens
|
598
629
|
)
|
630
|
+
except Exception as e:
|
631
|
+
full_text = f"{e}"
|
632
|
+
diff_text = ""
|
633
|
+
prompt_tokens = 0
|
634
|
+
completion_tokens = 0
|
635
|
+
total_tokens = 0
|
636
|
+
cost = 0
|
637
|
+
print(f"Error in LLM response {e}")
|
599
638
|
|
600
639
|
if(diff_text.strip() == ""):
|
601
640
|
print(f"\033[1;33mThere was no data in this diff. The LLM may have returned something invalid.\033[0m")
|
@@ -608,7 +647,7 @@ def main():
|
|
608
647
|
elif args.apply:
|
609
648
|
print("\nAttempting apply with the following diff:")
|
610
649
|
print("\n<diff>")
|
611
|
-
print(diff_text)
|
650
|
+
print(color_code_diff(diff_text))
|
612
651
|
print("\n</diff>")
|
613
652
|
print("Saved to patch.diff")
|
614
653
|
if apply_diff(project_dir, diff_text):
|
@@ -651,8 +690,11 @@ def main():
|
|
651
690
|
print("SMARTAPPLY")
|
652
691
|
print(file_diff)
|
653
692
|
print("-" * 40)
|
693
|
+
if args.applymodel is None:
|
694
|
+
args.applymodel = args.model
|
695
|
+
|
654
696
|
try:
|
655
|
-
updated_content = call_llm_for_apply_with_think_tool_available(file_path, original_content, file_diff, args.
|
697
|
+
updated_content = call_llm_for_apply_with_think_tool_available(file_path, original_content, file_diff, args.applymodel, extra_prompt=f"This changeset is from the following instructions:\n{user_prompt}", max_tokens=args.max_tokens)
|
656
698
|
|
657
699
|
if updated_content.strip() == "":
|
658
700
|
print("Cowardly refusing to write empty file to", file_path, "merge failed")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: gptdiff
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.9
|
4
4
|
Summary: A tool to generate and apply git diffs using LLMs
|
5
5
|
Author: 255labs
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
@@ -10,7 +10,7 @@ Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE.txt
|
11
11
|
Requires-Dist: openai>=1.0.0
|
12
12
|
Requires-Dist: tiktoken>=0.5.0
|
13
|
-
Requires-Dist: ai_agent_toolbox>=0.1.
|
13
|
+
Requires-Dist: ai_agent_toolbox>=0.1.11
|
14
14
|
Provides-Extra: test
|
15
15
|
Requires-Dist: pytest; extra == "test"
|
16
16
|
Requires-Dist: pytest-mock; extra == "test"
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
2
2
|
|
3
3
|
setup(
|
4
4
|
name='gptdiff',
|
5
|
-
version='0.1.
|
5
|
+
version='0.1.9',
|
6
6
|
description='A tool to generate and apply git diffs using LLMs',
|
7
7
|
author='255labs',
|
8
8
|
packages=find_packages(), # Use find_packages() to automatically discover packages
|
@@ -12,7 +12,7 @@ setup(
|
|
12
12
|
install_requires=[
|
13
13
|
'openai>=1.0.0',
|
14
14
|
'tiktoken>=0.5.0',
|
15
|
-
'ai_agent_toolbox>=0.1.
|
15
|
+
'ai_agent_toolbox>=0.1.11'
|
16
16
|
],
|
17
17
|
extras_require={
|
18
18
|
'test': ['pytest', 'pytest-mock'],
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import pytest
|
2
|
+
from gptdiff.gptdiff import color_code_diff
|
3
|
+
|
4
|
+
def test_color_code_diff():
|
5
|
+
"""
|
6
|
+
Test that lines starting with '-' are wrapped in red color codes
|
7
|
+
and lines starting with '+' are wrapped in green color codes.
|
8
|
+
"""
|
9
|
+
diff_text = """-this line is removed
|
10
|
+
+this line is added
|
11
|
+
unchanged line
|
12
|
+
-removed again
|
13
|
+
+and added again
|
14
|
+
some other neutral line"""
|
15
|
+
|
16
|
+
colorized = color_code_diff(diff_text)
|
17
|
+
|
18
|
+
# We expect lines beginning with '-' to be in red
|
19
|
+
assert "\033[31m-this line is removed\033[0m" in colorized
|
20
|
+
assert "\033[31m-removed again\033[0m" in colorized
|
21
|
+
|
22
|
+
# We expect lines beginning with '+' to be in green
|
23
|
+
assert "\033[32m+this line is added\033[0m" in colorized
|
24
|
+
assert "\033[32m+and added again\033[0m" in colorized
|
25
|
+
|
26
|
+
# Lines unchanged should remain uncolored
|
27
|
+
assert "unchanged line" in colorized
|
28
|
+
assert "some other neutral line" in colorized
|
29
|
+
|
30
|
+
# Ensure no erroneous color codes are added
|
31
|
+
# by counting them in the final output
|
32
|
+
color_code_count = colorized.count('\033[')
|
33
|
+
# We have four lines that should be color-coded, so we expect 4 * 2 = 8 color code inserts
|
34
|
+
# (start code + reset code per line)
|
35
|
+
assert color_code_count == 8
|
@@ -160,7 +160,7 @@ diff --git a/file2.py b/file2.py
|
|
160
160
|
}
|
161
161
|
|
162
162
|
# Mock LLM to return modified content based on file path
|
163
|
-
def mock_call_llm(file_path, original_content, file_diff, model, api_key, base_url):
|
163
|
+
def mock_call_llm(file_path, original_content, file_diff, model, api_key, base_url, extra_prompt=None, max_tokens=None):
|
164
164
|
if file_path == "file1.py":
|
165
165
|
return "def func1():\n print('New func1')"
|
166
166
|
elif file_path == "file2.py":
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|