regcode 0.6.2__tar.gz → 0.6.3__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.
Files changed (43) hide show
  1. {regcode-0.6.2 → regcode-0.6.3}/PKG-INFO +1 -1
  2. {regcode-0.6.2 → regcode-0.6.3}/pyproject.toml +1 -1
  3. {regcode-0.6.2 → regcode-0.6.3}/regcode/__init__.py +1 -1
  4. {regcode-0.6.2 → regcode-0.6.3}/regcode/main.py +270 -253
  5. {regcode-0.6.2 → regcode-0.6.3}/uv.lock +1 -1
  6. {regcode-0.6.2 → regcode-0.6.3}/.gitignore +0 -0
  7. {regcode-0.6.2 → regcode-0.6.3}/.gitlab-ci.yml +0 -0
  8. {regcode-0.6.2 → regcode-0.6.3}/.python-version +0 -0
  9. {regcode-0.6.2 → regcode-0.6.3}/.qwen/settings.json +0 -0
  10. {regcode-0.6.2 → regcode-0.6.3}/AGENTS.md +0 -0
  11. {regcode-0.6.2 → regcode-0.6.3}/GOALS.md +0 -0
  12. {regcode-0.6.2 → regcode-0.6.3}/README.md +0 -0
  13. {regcode-0.6.2 → regcode-0.6.3}/config.yaml.template +0 -0
  14. {regcode-0.6.2 → regcode-0.6.3}/main.py +0 -0
  15. {regcode-0.6.2 → regcode-0.6.3}/regcode/cli.py +0 -0
  16. {regcode-0.6.2 → regcode-0.6.3}/regcode/config.py +0 -0
  17. {regcode-0.6.2 → regcode-0.6.3}/regcode/conversation_manager.py +0 -0
  18. {regcode-0.6.2 → regcode-0.6.3}/regcode/dependencies.py +0 -0
  19. {regcode-0.6.2 → regcode-0.6.3}/regcode/monty_sandbox.py +0 -0
  20. {regcode-0.6.2 → regcode-0.6.3}/regcode/permissions.py +0 -0
  21. {regcode-0.6.2 → regcode-0.6.3}/regcode/sandbox.py +0 -0
  22. {regcode-0.6.2 → regcode-0.6.3}/regcode/tools/__init__.py +0 -0
  23. {regcode-0.6.2 → regcode-0.6.3}/regcode/tools/base.py +0 -0
  24. {regcode-0.6.2 → regcode-0.6.3}/regcode/tools/builtins.py +0 -0
  25. {regcode-0.6.2 → regcode-0.6.3}/regcode/tools/registry.py +0 -0
  26. {regcode-0.6.2 → regcode-0.6.3}/regcode/tools/review_notes.py +0 -0
  27. {regcode-0.6.2 → regcode-0.6.3}/regcode/tools/web_crawl.py +0 -0
  28. {regcode-0.6.2 → regcode-0.6.3}/regcode/tools/web_search.py +0 -0
  29. {regcode-0.6.2 → regcode-0.6.3}/regcode/tracing.py +0 -0
  30. {regcode-0.6.2 → regcode-0.6.3}/regcode/tui.py +0 -0
  31. {regcode-0.6.2 → regcode-0.6.3}/tests/__init__.py +0 -0
  32. {regcode-0.6.2 → regcode-0.6.3}/tests/conftest.py +0 -0
  33. {regcode-0.6.2 → regcode-0.6.3}/tests/test_api.py +0 -0
  34. {regcode-0.6.2 → regcode-0.6.3}/tests/test_cli.py +0 -0
  35. {regcode-0.6.2 → regcode-0.6.3}/tests/test_config.py +0 -0
  36. {regcode-0.6.2 → regcode-0.6.3}/tests/test_context_manager.py +0 -0
  37. {regcode-0.6.2 → regcode-0.6.3}/tests/test_dependencies.py +0 -0
  38. {regcode-0.6.2 → regcode-0.6.3}/tests/test_main.py +0 -0
  39. {regcode-0.6.2 → regcode-0.6.3}/tests/test_monty_sandbox.py +0 -0
  40. {regcode-0.6.2 → regcode-0.6.3}/tests/test_osv_cve.py +0 -0
  41. {regcode-0.6.2 → regcode-0.6.3}/tests/test_provider_extra.py +0 -0
  42. {regcode-0.6.2 → regcode-0.6.3}/tests/test_review_notes.py +0 -0
  43. {regcode-0.6.2 → regcode-0.6.3}/tests/test_web_search.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: regcode
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: Minimalistic coding agent with Python API and CLI
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: click>=8.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "regcode"
3
- version = "0.6.2"
3
+ version = "0.6.3"
4
4
  description = "Minimalistic coding agent with Python API and CLI"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -2,4 +2,4 @@ from regcode.config import Config
2
2
  from regcode.main import Agent, AgentStatus, ToolCall
3
3
 
4
4
  __all__ = ["Agent", "Config", "AgentStatus", "ToolCall"]
5
- __version__ = "0.6.2"
5
+ __version__ = "0.6.3"
@@ -263,7 +263,7 @@ class Agent:
263
263
  self._track_response_cost(response)
264
264
 
265
265
  choice = response.choices[0] # type: ignore
266
- message_obj = choice.message
266
+ message_obj = choice.message # type: ignore
267
267
  response_message = {
268
268
  "role": "assistant",
269
269
  "content": message_obj.content or "",
@@ -513,231 +513,270 @@ class Agent:
513
513
  return "".join(parts)
514
514
 
515
515
  def _stream(
516
- self,
517
- messages: list[dict],
518
- tool_definitions: list[dict[str, Any]] | None = None,
519
- on_text_chunk: Callable[[str], None] | None = None,
520
- ) -> str:
521
- """Stream response with tool support.
522
-
523
- Runs the same tool-call loop as chat() but streams the final
524
- text response chunk-by-chunk. Tool calls are executed normally
525
- and their results are communicated via the callbacks registered
526
- on __init__ (status_callback / result_callback).
527
-
528
- Args:
529
- messages: The message list for the completion.
530
- tool_definitions: Tool definitions for the LLM.
531
- on_text_chunk: Optional callback invoked for each text chunk
532
- from the LLM stream. Useful for the CLI to print
533
- progressively.
534
-
535
- Returns:
536
- The complete final text response.
537
- """
538
- completion_kwargs = self.config.get_litellm_completion_kwargs()
539
- completion_kwargs["messages"] = messages
540
- if tool_definitions:
541
- completion_kwargs["tools"] = tool_definitions
542
- completion_kwargs["tool_choice"] = "auto"
516
+ self,
517
+ messages: list[dict],
518
+ tool_definitions: list[dict[str, Any]] | None = None,
519
+ on_text_chunk: Callable[[str], None] | None = None,
520
+ max_tool_calls: int = 10,
521
+ ) -> str:
522
+ """Stream response with tool support.
523
+
524
+ Runs the same tool-call loop as chat() but streams the final
525
+ text response chunk-by-chunk. Tool calls are executed normally
526
+ and their results are communicated via the callbacks registered
527
+ on __init__ (status_callback / result_callback).
528
+
529
+ Args:
530
+ messages: The message list for the completion.
531
+ tool_definitions: Tool definitions for the LLM.
532
+ on_text_chunk: Optional callback invoked for each text chunk
533
+ from the LLM stream. Useful for the CLI to print
534
+ progressively.
535
+
536
+ Returns:
537
+ The complete final text response.
538
+ """
539
+ completion_kwargs = self.config.get_litellm_completion_kwargs()
540
+ completion_kwargs["messages"] = messages
541
+ if tool_definitions:
542
+ completion_kwargs["tools"] = tool_definitions
543
+ completion_kwargs["tool_choice"] = "auto"
543
544
 
544
- full_text = ""
545
- final_text = ""
546
- response_message: dict[str, Any] = {}
547
- total_tool_calls = 0
545
+ full_text = ""
546
+ final_text = ""
547
+ response_message: dict[str, Any] = {}
548
+ total_tool_calls = 0
548
549
 
549
- for _ in range(10):
550
- # When budget is exhausted, rebuild system prompt without tools
551
- if self._tool_budget_exhausted:
550
+ for _ in range(max_tool_calls):
552
551
  system_prompt_text = self._build_system_prompt_no_tools()
553
552
  messages = [
554
553
  {"role": "system", "content": system_prompt_text},
555
554
  *messages[1:],
556
555
  ]
557
556
 
558
- current_tool_defs = (
559
- tool_definitions if not self._tool_budget_exhausted else None
560
- )
561
-
562
- if current_tool_defs:
563
- completion_kwargs["tools"] = current_tool_defs
564
- completion_kwargs["tool_choice"] = "auto"
565
- elif self._tool_budget_exhausted:
566
- completion_kwargs["tool_choice"] = "none"
567
- # Remove tools key if present
568
- completion_kwargs.pop("tools", None)
569
-
570
- completion_kwargs["stream"] = True
571
- completion_kwargs["messages"] = messages
572
-
573
- response = litellm.completion(**completion_kwargs)
574
-
575
- # Track cost and tokens from this response
576
- self._track_response_cost(response)
577
-
578
- # Stream the response — accumulate text and tool_calls
579
- # across ALL chunks, not just the last one.
580
- full_text = ""
581
- # Accumulate delta tool calls by index so we can merge partial
582
- # chunks (id in first chunk, function name in second, arguments
583
- # accumulating in subsequent chunks) into a single per-call object.
584
- accumulated_by_index: dict[int, dict[str, Any]] = {}
585
- try:
586
- for chunk in response:
587
- delta = chunk.choices[0].delta # type: ignore
588
- # Accumulate text content
589
- if delta.content:
590
- full_text += delta.content
591
- if on_text_chunk:
592
- on_text_chunk(delta.content)
593
- # Accumulate tool calls from any chunk
594
- if delta.tool_calls:
595
- for tc_delta in delta.tool_calls:
596
- idx = tc_delta.index # type: ignore
597
- if idx not in accumulated_by_index:
598
- accumulated_by_index[idx] = {
599
- "id": tc_delta.id or "",
600
- "type": tc_delta.type or "function",
601
- "function_name": "",
602
- "function_arguments": "",
603
- }
604
- acc = accumulated_by_index[idx]
605
- if tc_delta.id is not None:
606
- acc["id"] = tc_delta.id
607
- if tc_delta.function is not None:
608
- if tc_delta.function.name:
609
- acc["function_name"] = tc_delta.function.name
610
- if tc_delta.function.arguments:
611
- acc["function_arguments"] += (
612
- tc_delta.function.arguments
613
- )
614
- except Exception:
615
- # Litellm's streaming iterator can crash on malformed responses
616
- # or sentinel markers like [DONE]. If iteration fails, just use
617
- # whatever text we accumulated so far.
618
- pass
619
-
620
- # Reconstruct tool call dicts from accumulated chunks and validate
621
- valid_tool_calls = []
622
- for idx, acc in accumulated_by_index.items():
623
- if not acc.get("id") or not acc.get("function_name"):
624
- continue
625
- valid_tool_calls.append({
626
- "id": acc["id"],
627
- "type": acc["type"],
628
- "index": idx,
629
- "function": {
630
- "name": acc["function_name"],
631
- "arguments": acc["function_arguments"],
632
- },
633
- })
634
-
635
- response_message = {
636
- "role": "assistant",
637
- "content": full_text or "",
638
- }
639
-
640
- # Attach only valid accumulated tool calls
641
- if valid_tool_calls:
642
- response_message["tool_calls"] = valid_tool_calls
643
-
644
- messages.append(response_message)
645
-
646
- if full_text:
647
- final_text = full_text
557
+ current_tool_defs = (
558
+ tool_definitions if not self._tool_budget_exhausted else None
559
+ )
648
560
 
649
- if self._tool_budget_exhausted:
650
- break
561
+ if current_tool_defs:
562
+ completion_kwargs["tools"] = current_tool_defs
563
+ completion_kwargs["tool_choice"] = "auto"
564
+ elif self._tool_budget_exhausted:
565
+ completion_kwargs["tool_choice"] = "none"
566
+ # Remove tools key if present
567
+ completion_kwargs.pop("tools", None)
568
+
569
+ completion_kwargs["stream"] = True
570
+ completion_kwargs["messages"] = messages
571
+
572
+ response = litellm.completion(**completion_kwargs)
573
+
574
+ # Track cost and tokens from this response
575
+ self._track_response_cost(response)
576
+
577
+ # Stream the response — accumulate text and tool_calls
578
+ # across ALL chunks, not just the last one.
579
+ full_text = ""
580
+ # Accumulate delta tool calls by index so we can merge partial
581
+ # chunks (id in first chunk, function name in second, arguments
582
+ # accumulating in subsequent chunks) into a single per-call object.
583
+ accumulated_by_index: dict[int, dict[str, Any]] = {}
584
+ try:
585
+ for chunk in response:
586
+ delta = chunk.choices[0].delta # type: ignore
587
+ # Accumulate text content
588
+ if delta.content:
589
+ full_text += delta.content
590
+ if on_text_chunk:
591
+ on_text_chunk(delta.content)
592
+ # Accumulate tool calls from any chunk
593
+ if delta.tool_calls:
594
+ for tc_delta in delta.tool_calls:
595
+ idx = tc_delta.index # type: ignore
596
+ if idx not in accumulated_by_index:
597
+ accumulated_by_index[idx] = {
598
+ "id": tc_delta.id or "",
599
+ "type": tc_delta.type or "function",
600
+ "function_name": "",
601
+ "function_arguments": "",
602
+ }
603
+ acc = accumulated_by_index[idx]
604
+ if tc_delta.id is not None:
605
+ acc["id"] = tc_delta.id
606
+ if tc_delta.function is not None:
607
+ if tc_delta.function.name:
608
+ acc["function_name"] = tc_delta.function.name
609
+ if tc_delta.function.arguments:
610
+ acc["function_arguments"] += (
611
+ tc_delta.function.arguments
612
+ )
613
+ except Exception:
614
+ # Litellm's streaming iterator can crash on malformed responses
615
+ # or sentinel markers like [DONE]. If iteration fails, just use
616
+ # whatever text we accumulated so far.
617
+ pass
618
+
619
+ # Reconstruct tool call dicts from accumulated chunks and validate
620
+ valid_tool_calls = []
621
+ for idx, acc in accumulated_by_index.items():
622
+ if not acc.get("id") or not acc.get("function_name"):
623
+ continue
624
+ valid_tool_calls.append({
625
+ "id": acc["id"],
626
+ "type": acc["type"],
627
+ "index": idx,
628
+ "function": {
629
+ "name": acc["function_name"],
630
+ "arguments": acc["function_arguments"],
631
+ },
632
+ })
633
+
634
+ response_message = {
635
+ "role": "assistant",
636
+ "content": full_text or "",
637
+ }
638
+
639
+ # Attach only valid accumulated tool calls
640
+ if valid_tool_calls:
641
+ response_message["tool_calls"] = valid_tool_calls
642
+
643
+ messages.append(response_message)
644
+
645
+ if full_text:
646
+ final_text = full_text
647
+
648
+ if self._tool_budget_exhausted:
649
+ break
651
650
 
652
- if valid_tool_calls:
653
- for tc_dict in valid_tool_calls:
654
- tool_name = str(tc_dict["function"]["name"])
655
- tool_call_obj = ToolCall(tool_name, {})
651
+ if valid_tool_calls:
652
+ for tc_dict in valid_tool_calls:
653
+ tool_name = str(tc_dict["function"]["name"])
654
+ tool_call_obj = ToolCall(tool_name, {})
655
+
656
+ try:
657
+ tool_args = json.loads(tc_dict["function"]["arguments"])
658
+ except (json.JSONDecodeError, AttributeError):
659
+ tool_args = {}
660
+ if self._status_callback is not None:
661
+ self._status_callback(
662
+ AgentStatus.TOOL_CALL, tool_call_obj
663
+ )
664
+ if self._result_callback is not None:
665
+ self._result_callback(
666
+ AgentStatus.TOOL_RESULT,
667
+ (
668
+ f"Error: Failed to parse arguments for "
669
+ f"tool '{tool_name}'."
670
+ ),
671
+ True,
672
+ tool_call_obj,
673
+ )
674
+ messages.append({
675
+ "role": "tool",
676
+ "tool_call_id": tc_dict["id"],
677
+ "name": tool_name,
678
+ "content": (
679
+ f"Error: Failed to parse arguments for "
680
+ f"tool '{tool_name}'."
681
+ ),
682
+ })
683
+ total_tool_calls += 1
684
+ continue
685
+
686
+ if self._allowed_dir is not None:
687
+ for arg_name in ["path", "file_path"]:
688
+ if arg_name in tool_args:
689
+ arg_path = Path(tool_args[arg_name])
690
+ try:
691
+ if arg_path.is_absolute():
692
+ raise ValueError(
693
+ "Access denied: "
694
+ "absolute paths are not allowed"
695
+ )
696
+ if ".." in arg_path.parts:
697
+ raise ValueError(
698
+ "Access denied: "
699
+ "paths with '..' are not allowed"
700
+ )
701
+ if not arg_path.is_absolute():
702
+ arg_path = Path(
703
+ self._allowed_dir
704
+ ) / arg_path
705
+ arg_path = arg_path.resolve()
706
+ if not arg_path.is_relative_to(
707
+ Path(self._allowed_dir).resolve()
708
+ ):
709
+ raise ValueError(
710
+ (
711
+ f"Access denied: "
712
+ f"'{arg_path}' is outside "
713
+ f"allowed directory "
714
+ f"'{self._allowed_dir}'"
715
+ )
716
+ )
717
+ except ValueError as e:
718
+ messages.append(
719
+ {
720
+ "role": "tool",
721
+ "tool_call_id": tc_dict["id"],
722
+ "name": tool_name,
723
+ "content": str(e),
724
+ }
725
+ )
726
+ continue
656
727
 
657
- try:
658
- tool_args = json.loads(tc_dict["function"]["arguments"])
659
- except (json.JSONDecodeError, AttributeError):
660
- tool_args = {}
728
+ tool_call_obj = ToolCall(tool_name, tool_args)
729
+ if self._status_callback is not None:
730
+ self._status_callback(
731
+ AgentStatus.TOOL_CALL, tool_call_obj
732
+ )
733
+ result = self.registry.execute(tool_name, **tool_args)
734
+ result_str = str(result)
661
735
  if self._status_callback is not None:
662
- self._status_callback(AgentStatus.TOOL_CALL, tool_call_obj)
736
+ self._status_callback(
737
+ AgentStatus.TOOL_RESULT, tool_call_obj
738
+ )
663
739
  if self._result_callback is not None:
740
+ is_error = (
741
+ "Error" in result_str
742
+ or "Traceback" in result_str
743
+ )
664
744
  self._result_callback(
665
745
  AgentStatus.TOOL_RESULT,
666
- (
667
- f"Error: Failed to parse arguments for "
668
- f"tool '{tool_name}'."
669
- ),
670
- True,
746
+ result_str,
747
+ is_error,
671
748
  tool_call_obj,
672
749
  )
673
- messages.append({
674
- "role": "tool",
675
- "tool_call_id": tc_dict["id"],
676
- "name": tool_name,
677
- "content": (
678
- f"Error: Failed to parse arguments for "
679
- f"tool '{tool_name}'."
680
- ),
681
- })
682
- total_tool_calls += 1
683
- continue
684
750
 
685
- if self._allowed_dir is not None:
686
- for arg_name in ["path", "file_path"]:
687
- if arg_name in tool_args:
688
- arg_path = Path(tool_args[arg_name])
689
- try:
690
- if arg_path.is_absolute():
691
- raise ValueError(
692
- "Access denied: "
693
- "absolute paths are not allowed"
694
- )
695
- if ".." in arg_path.parts:
696
- raise ValueError(
697
- "Access denied: "
698
- "paths with '..' are not allowed"
699
- )
700
- if not arg_path.is_absolute():
701
- arg_path = Path(self._allowed_dir) / arg_path
702
- arg_path = arg_path.resolve()
703
- if not arg_path.is_relative_to(
704
- Path(self._allowed_dir).resolve()
705
- ):
706
- raise ValueError(
707
- f"Access denied: '{arg_path}' is outside "
708
- f"allowed directory '{self._allowed_dir}'"
709
- )
710
- except ValueError as e:
711
- messages.append(
712
- {
713
- "role": "tool",
714
- "tool_call_id": tc_dict["id"],
715
- "name": tool_name,
716
- "content": str(e),
717
- }
718
- )
719
- continue
720
-
721
- tool_call_obj = ToolCall(tool_name, tool_args)
722
- if self._status_callback is not None:
723
- self._status_callback(AgentStatus.TOOL_CALL, tool_call_obj)
724
- result = self.registry.execute(tool_name, **tool_args)
725
- result_str = str(result)
726
- if self._status_callback is not None:
727
- self._status_callback(AgentStatus.TOOL_RESULT, tool_call_obj)
728
- if self._result_callback is not None:
729
- is_error = "Error" in result_str or "Traceback" in result_str
730
- self._result_callback(
731
- AgentStatus.TOOL_RESULT, result_str, is_error, tool_call_obj
732
- )
733
-
734
- self._tool_budget_remaining -= 1
735
- if self._tool_budget_remaining <= 0:
736
- self._tool_budget_exhausted = True
737
- if self._status_callback is not None:
738
- self._status_callback(
739
- AgentStatus.BUDGET_EXHAUSTED, tool_call_obj
751
+ self._tool_budget_remaining -= 1
752
+ if self._tool_budget_remaining <= 0:
753
+ self._tool_budget_exhausted = True
754
+ if self._status_callback is not None:
755
+ self._status_callback(
756
+ AgentStatus.BUDGET_EXHAUSTED, tool_call_obj
757
+ )
758
+ messages.append(
759
+ {
760
+ "role": "tool",
761
+ "tool_call_id": tc_dict["id"],
762
+ "name": tool_name,
763
+ "content": str(result),
764
+ }
740
765
  )
766
+ messages.append(
767
+ {
768
+ "role": "user",
769
+ "content": (
770
+ "You have exhausted your tool budget. "
771
+ "Provide your final answer in plain text only. "
772
+ "Do not use any special formatting, tags, or "
773
+ "code markers. Just the answer."
774
+ ),
775
+ }
776
+ )
777
+ total_tool_calls += 1
778
+ continue
779
+
741
780
  messages.append(
742
781
  {
743
782
  "role": "tool",
@@ -746,61 +785,39 @@ class Agent:
746
785
  "content": str(result),
747
786
  }
748
787
  )
749
- messages.append(
750
- {
751
- "role": "user",
752
- "content": (
753
- "You have exhausted your tool budget. "
754
- "Provide your final answer in plain text only. "
755
- "Do not use any special formatting, tags, or "
756
- "code markers. Just the answer."
757
- ),
758
- }
759
- )
760
788
  total_tool_calls += 1
761
- continue
789
+ self._update_context_progress(messages)
790
+ if total_tool_calls >= 30:
791
+ break
792
+ continue
762
793
 
794
+ # Model returned neither text content nor tool calls — re-prompt it
795
+ # to force an answer with the current context state.
796
+ if not full_text and (
797
+ not response_message.get("tool_calls") or
798
+ len(response_message.get("tool_calls", [])) == 0
799
+ ):
800
+ if total_tool_calls >= 5:
801
+ # After 5 empty responses, just give up and return empty
802
+ break
763
803
  messages.append(
764
804
  {
765
- "role": "tool",
766
- "tool_call_id": tc_dict["id"],
767
- "name": tool_name,
768
- "content": str(result),
805
+ "role": "user",
806
+ "content": (
807
+ "You returned an empty response with no tool calls. "
808
+ "Please provide your answer now based on the current "
809
+ "conversation state. Respond in plain text only."
810
+ ),
769
811
  }
770
812
  )
771
813
  total_tool_calls += 1
772
- self._update_context_progress(messages)
773
- if total_tool_calls >= 30:
774
- break
775
- continue
776
-
777
- # Model returned neither text content nor tool calls — re-prompt it
778
- # to force an answer with the current context state.
779
- if not full_text and (
780
- not response_message.get("tool_calls") or
781
- len(response_message.get("tool_calls", [])) == 0
782
- ):
783
- if total_tool_calls >= 5:
784
- # After 5 empty responses, just give up and return empty
785
- break
786
- messages.append(
787
- {
788
- "role": "user",
789
- "content": (
790
- "You returned an empty response with no tool calls. "
791
- "Please provide your answer now based on the current "
792
- "conversation state. Respond in plain text only."
793
- ),
794
- }
795
- )
796
- total_tool_calls += 1
797
- continue
814
+ continue
798
815
 
799
- break
816
+ break
800
817
 
801
- self._update_context_progress(messages)
802
- self._history = [m for m in messages if m.get("role") != "system"]
803
- return final_text or response_message.get("content", "") or ""
818
+ self._update_context_progress(messages)
819
+ self._history = [m for m in messages if m.get("role") != "system"]
820
+ return final_text or response_message.get("content", "") or ""
804
821
 
805
822
  def _update_context_progress(self, messages: list[dict[str, Any]]) -> None:
806
823
  """Estimate token usage and report progress via context_callback."""
@@ -3805,7 +3805,7 @@ wheels = [
3805
3805
 
3806
3806
  [[package]]
3807
3807
  name = "regcode"
3808
- version = "0.6.2"
3808
+ version = "0.6.3"
3809
3809
  source = { editable = "." }
3810
3810
  dependencies = [
3811
3811
  { name = "click" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes