gptdiff 0.1.8__tar.gz → 0.1.9__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: gptdiff
3
- Version: 0.1.8
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
@@ -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=None, base_url=None)
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=30000)
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
- full_text, diff_text, prompt_tokens, completion_tokens, total_tokens, cost = call_llm_for_diff(system_prompt, user_prompt, files_content, args.model,
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.model)
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.8
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
@@ -9,4 +9,5 @@ gptdiff.egg-info/dependency_links.txt
9
9
  gptdiff.egg-info/entry_points.txt
10
10
  gptdiff.egg-info/requires.txt
11
11
  gptdiff.egg-info/top_level.txt
12
+ tests/test_diff_parse.py
12
13
  tests/test_smartapply.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='gptdiff',
5
- version='0.1.8',
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
@@ -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