gptdiff 0.1.8__tar.gz → 0.1.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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