tunacode-cli 0.0.45__py3-none-any.whl → 0.0.47__py3-none-any.whl
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.
Potentially problematic release.
This version of tunacode-cli might be problematic. Click here for more details.
- tunacode/cli/repl.py +1 -2
- tunacode/constants.py +1 -1
- tunacode/core/agents/main.py +295 -280
- tunacode/core/token_usage/cost_calculator.py +8 -0
- tunacode/core/token_usage/usage_tracker.py +17 -1
- tunacode/ui/input.py +2 -1
- tunacode/ui/keybindings.py +17 -1
- {tunacode_cli-0.0.45.dist-info → tunacode_cli-0.0.47.dist-info}/METADATA +32 -6
- {tunacode_cli-0.0.45.dist-info → tunacode_cli-0.0.47.dist-info}/RECORD +13 -13
- {tunacode_cli-0.0.45.dist-info → tunacode_cli-0.0.47.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.45.dist-info → tunacode_cli-0.0.47.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.45.dist-info → tunacode_cli-0.0.47.dist-info}/licenses/LICENSE +0 -0
- {tunacode_cli-0.0.45.dist-info → tunacode_cli-0.0.47.dist-info}/top_level.txt +0 -0
tunacode/cli/repl.py
CHANGED
|
@@ -344,7 +344,7 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
|
|
|
344
344
|
except CancelledError:
|
|
345
345
|
await ui.muted(MSG_REQUEST_CANCELLED)
|
|
346
346
|
except UserAbortError:
|
|
347
|
-
await ui.muted(
|
|
347
|
+
await ui.muted(MSG_OPERATION_ABORTED_BY_USER)
|
|
348
348
|
except UnexpectedModelBehavior as e:
|
|
349
349
|
error_message = str(e)
|
|
350
350
|
await ui.muted(error_message)
|
|
@@ -463,7 +463,6 @@ async def repl(state_manager: StateManager):
|
|
|
463
463
|
state_manager.session.current_task = get_app().create_background_task(
|
|
464
464
|
process_request(line, state_manager)
|
|
465
465
|
)
|
|
466
|
-
await state_manager.session.current_task
|
|
467
466
|
|
|
468
467
|
state_manager.session.update_token_count()
|
|
469
468
|
context_display = get_context_window_display(
|
tunacode/constants.py
CHANGED
tunacode/core/agents/main.py
CHANGED
|
@@ -34,7 +34,7 @@ from tunacode.core.recursive import RecursiveTaskExecutor
|
|
|
34
34
|
from tunacode.core.state import StateManager
|
|
35
35
|
from tunacode.core.token_usage.api_response_parser import ApiResponseParser
|
|
36
36
|
from tunacode.core.token_usage.cost_calculator import CostCalculator
|
|
37
|
-
from tunacode.exceptions import ToolBatchingJSONError
|
|
37
|
+
from tunacode.exceptions import ToolBatchingJSONError, UserAbortError
|
|
38
38
|
from tunacode.services.mcp import get_mcp_servers
|
|
39
39
|
from tunacode.tools.bash import bash
|
|
40
40
|
from tunacode.tools.glob import glob
|
|
@@ -752,300 +752,335 @@ async def process_request(
|
|
|
752
752
|
tool_callback: Optional[ToolCallback] = None,
|
|
753
753
|
streaming_callback: Optional[callable] = None,
|
|
754
754
|
) -> AgentRun:
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
755
|
+
try:
|
|
756
|
+
agent = get_or_create_agent(model, state_manager)
|
|
757
|
+
mh = state_manager.session.messages.copy()
|
|
758
|
+
# Get max iterations from config (default: 40)
|
|
759
|
+
max_iterations = state_manager.session.user_config.get("settings", {}).get(
|
|
760
|
+
"max_iterations", 40
|
|
761
|
+
)
|
|
762
|
+
fallback_enabled = state_manager.session.user_config.get("settings", {}).get(
|
|
763
|
+
"fallback_response", True
|
|
764
|
+
)
|
|
762
765
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
766
|
+
# Check if recursive execution is enabled
|
|
767
|
+
use_recursive = state_manager.session.user_config.get("settings", {}).get(
|
|
768
|
+
"use_recursive_execution", True
|
|
769
|
+
)
|
|
770
|
+
recursive_threshold = state_manager.session.user_config.get("settings", {}).get(
|
|
771
|
+
"recursive_complexity_threshold", 0.7
|
|
772
|
+
)
|
|
770
773
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
774
|
+
# Check if recursive execution should be used
|
|
775
|
+
if use_recursive and state_manager.session.current_recursion_depth == 0:
|
|
776
|
+
try:
|
|
777
|
+
# Initialize recursive executor
|
|
778
|
+
recursive_executor = RecursiveTaskExecutor(
|
|
779
|
+
state_manager=state_manager,
|
|
780
|
+
max_depth=state_manager.session.max_recursion_depth,
|
|
781
|
+
min_complexity_threshold=recursive_threshold,
|
|
782
|
+
default_iteration_budget=max_iterations,
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
# Analyze task complexity
|
|
786
|
+
complexity_result = await recursive_executor.decomposer.analyze_and_decompose(
|
|
787
|
+
message
|
|
788
|
+
)
|
|
781
789
|
|
|
782
|
-
|
|
783
|
-
|
|
790
|
+
if (
|
|
791
|
+
complexity_result.should_decompose
|
|
792
|
+
and complexity_result.total_complexity >= recursive_threshold
|
|
793
|
+
):
|
|
794
|
+
if state_manager.session.show_thoughts:
|
|
795
|
+
from tunacode.ui import console as ui
|
|
784
796
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
from tunacode.ui import console as ui
|
|
797
|
+
await ui.muted(
|
|
798
|
+
f"\n🔄 RECURSIVE EXECUTION: Task complexity {complexity_result.total_complexity:.2f} >= {recursive_threshold}"
|
|
799
|
+
)
|
|
800
|
+
await ui.muted(f"Reasoning: {complexity_result.reasoning}")
|
|
801
|
+
await ui.muted(f"Subtasks: {len(complexity_result.subtasks)}")
|
|
791
802
|
|
|
792
|
-
|
|
793
|
-
|
|
803
|
+
# Execute recursively
|
|
804
|
+
success, result, error = await recursive_executor.execute_task(
|
|
805
|
+
request=message, parent_task_id=None, depth=0
|
|
794
806
|
)
|
|
795
|
-
await ui.muted(f"Reasoning: {complexity_result.reasoning}")
|
|
796
|
-
await ui.muted(f"Subtasks: {len(complexity_result.subtasks)}")
|
|
797
807
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
808
|
+
# For now, fall back to normal execution
|
|
809
|
+
# TODO: Properly integrate recursive execution results
|
|
810
|
+
pass
|
|
811
|
+
except Exception as e:
|
|
812
|
+
logger.warning(f"Recursive execution failed, falling back to normal: {e}")
|
|
813
|
+
# Continue with normal execution
|
|
802
814
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
pass
|
|
806
|
-
except Exception as e:
|
|
807
|
-
logger.warning(f"Recursive execution failed, falling back to normal: {e}")
|
|
808
|
-
# Continue with normal execution
|
|
809
|
-
|
|
810
|
-
from tunacode.configuration.models import ModelRegistry
|
|
811
|
-
from tunacode.core.token_usage.usage_tracker import UsageTracker
|
|
812
|
-
|
|
813
|
-
parser = ApiResponseParser()
|
|
814
|
-
registry = ModelRegistry()
|
|
815
|
-
calculator = CostCalculator(registry)
|
|
816
|
-
usage_tracker = UsageTracker(parser, calculator, state_manager)
|
|
817
|
-
response_state = ResponseState()
|
|
818
|
-
|
|
819
|
-
# Reset iteration tracking for this request
|
|
820
|
-
state_manager.session.iteration_count = 0
|
|
821
|
-
|
|
822
|
-
# Create a request-level buffer for batching read-only tools across nodes
|
|
823
|
-
tool_buffer = ToolBuffer()
|
|
824
|
-
|
|
825
|
-
# Show TUNACODE.md preview if it was loaded and thoughts are enabled
|
|
826
|
-
if state_manager.session.show_thoughts and hasattr(state_manager, "tunacode_preview"):
|
|
827
|
-
from tunacode.ui import console as ui
|
|
828
|
-
|
|
829
|
-
await ui.muted(state_manager.tunacode_preview)
|
|
830
|
-
# Clear the preview after displaying it once
|
|
831
|
-
delattr(state_manager, "tunacode_preview")
|
|
832
|
-
|
|
833
|
-
# Show what we're sending to the API when thoughts are enabled
|
|
834
|
-
if state_manager.session.show_thoughts:
|
|
835
|
-
from tunacode.ui import console as ui
|
|
836
|
-
|
|
837
|
-
await ui.muted("\n" + "=" * 60)
|
|
838
|
-
await ui.muted("📤 SENDING TO API:")
|
|
839
|
-
await ui.muted(f"Message: {message}")
|
|
840
|
-
await ui.muted(f"Model: {model}")
|
|
841
|
-
await ui.muted(f"Message History Length: {len(mh)}")
|
|
842
|
-
await ui.muted("=" * 60)
|
|
843
|
-
|
|
844
|
-
async with agent.iter(message, message_history=mh) as agent_run:
|
|
845
|
-
i = 0
|
|
846
|
-
async for node in agent_run:
|
|
847
|
-
state_manager.session.current_iteration = i + 1
|
|
848
|
-
|
|
849
|
-
# Handle token-level streaming for model request nodes
|
|
850
|
-
if streaming_callback and STREAMING_AVAILABLE and Agent.is_model_request_node(node):
|
|
851
|
-
async with node.stream(agent_run.ctx) as request_stream:
|
|
852
|
-
async for event in request_stream:
|
|
853
|
-
if isinstance(event, PartDeltaEvent) and isinstance(
|
|
854
|
-
event.delta, TextPartDelta
|
|
855
|
-
):
|
|
856
|
-
# Stream individual token deltas
|
|
857
|
-
if event.delta.content_delta:
|
|
858
|
-
await streaming_callback(event.delta.content_delta)
|
|
859
|
-
|
|
860
|
-
await _process_node(
|
|
861
|
-
node,
|
|
862
|
-
tool_callback,
|
|
863
|
-
state_manager,
|
|
864
|
-
tool_buffer,
|
|
865
|
-
streaming_callback,
|
|
866
|
-
usage_tracker,
|
|
867
|
-
)
|
|
868
|
-
if hasattr(node, "result") and node.result and hasattr(node.result, "output"):
|
|
869
|
-
if node.result.output:
|
|
870
|
-
response_state.has_user_response = True
|
|
871
|
-
i += 1
|
|
872
|
-
state_manager.session.iteration_count = i
|
|
815
|
+
from tunacode.configuration.models import ModelRegistry
|
|
816
|
+
from tunacode.core.token_usage.usage_tracker import UsageTracker
|
|
873
817
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
818
|
+
parser = ApiResponseParser()
|
|
819
|
+
registry = ModelRegistry()
|
|
820
|
+
calculator = CostCalculator(registry)
|
|
821
|
+
usage_tracker = UsageTracker(parser, calculator, state_manager)
|
|
822
|
+
response_state = ResponseState()
|
|
877
823
|
|
|
878
|
-
|
|
824
|
+
# Reset iteration tracking for this request
|
|
825
|
+
state_manager.session.iteration_count = 0
|
|
879
826
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
tool_summary = {}
|
|
883
|
-
for tc in state_manager.session.tool_calls:
|
|
884
|
-
tool_name = tc.get("tool", "unknown")
|
|
885
|
-
tool_summary[tool_name] = tool_summary.get(tool_name, 0) + 1
|
|
827
|
+
# Create a request-level buffer for batching read-only tools across nodes
|
|
828
|
+
tool_buffer = ToolBuffer()
|
|
886
829
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
830
|
+
# Show TUNACODE.md preview if it was loaded and thoughts are enabled
|
|
831
|
+
if state_manager.session.show_thoughts and hasattr(state_manager, "tunacode_preview"):
|
|
832
|
+
from tunacode.ui import console as ui
|
|
833
|
+
|
|
834
|
+
await ui.muted(state_manager.tunacode_preview)
|
|
835
|
+
# Clear the preview after displaying it once
|
|
836
|
+
delattr(state_manager, "tunacode_preview")
|
|
837
|
+
|
|
838
|
+
# Show what we're sending to the API when thoughts are enabled
|
|
839
|
+
if state_manager.session.show_thoughts:
|
|
840
|
+
from tunacode.ui import console as ui
|
|
841
|
+
|
|
842
|
+
await ui.muted("\n" + "=" * 60)
|
|
843
|
+
await ui.muted("📤 SENDING TO API:")
|
|
844
|
+
await ui.muted(f"Message: {message}")
|
|
845
|
+
await ui.muted(f"Model: {model}")
|
|
846
|
+
await ui.muted(f"Message History Length: {len(mh)}")
|
|
847
|
+
await ui.muted("=" * 60)
|
|
848
|
+
|
|
849
|
+
async with agent.iter(message, message_history=mh) as agent_run:
|
|
850
|
+
i = 0
|
|
851
|
+
async for node in agent_run:
|
|
852
|
+
state_manager.session.current_iteration = i + 1
|
|
853
|
+
|
|
854
|
+
# Handle token-level streaming for model request nodes
|
|
855
|
+
if streaming_callback and STREAMING_AVAILABLE and Agent.is_model_request_node(node):
|
|
856
|
+
async with node.stream(agent_run.ctx) as request_stream:
|
|
857
|
+
async for event in request_stream:
|
|
858
|
+
if isinstance(event, PartDeltaEvent) and isinstance(
|
|
859
|
+
event.delta, TextPartDelta
|
|
860
|
+
):
|
|
861
|
+
# Stream individual token deltas
|
|
862
|
+
if event.delta.content_delta:
|
|
863
|
+
await streaming_callback(event.delta.content_delta)
|
|
864
|
+
|
|
865
|
+
await _process_node(
|
|
866
|
+
node,
|
|
867
|
+
tool_callback,
|
|
868
|
+
state_manager,
|
|
869
|
+
tool_buffer,
|
|
870
|
+
streaming_callback,
|
|
871
|
+
usage_tracker,
|
|
872
|
+
)
|
|
873
|
+
if hasattr(node, "result") and node.result and hasattr(node.result, "output"):
|
|
874
|
+
if node.result.output:
|
|
875
|
+
response_state.has_user_response = True
|
|
876
|
+
i += 1
|
|
877
|
+
state_manager.session.iteration_count = i
|
|
891
878
|
|
|
892
|
-
|
|
879
|
+
# Display iteration progress if thoughts are enabled
|
|
893
880
|
if state_manager.session.show_thoughts:
|
|
894
881
|
from tunacode.ui import console as ui
|
|
895
882
|
|
|
896
|
-
await ui.
|
|
897
|
-
break
|
|
883
|
+
await ui.muted(f"\nITERATION: {i}/{max_iterations}")
|
|
898
884
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
885
|
+
# Show summary of tools used so far
|
|
886
|
+
if state_manager.session.tool_calls:
|
|
887
|
+
tool_summary = {}
|
|
888
|
+
for tc in state_manager.session.tool_calls:
|
|
889
|
+
tool_name = tc.get("tool", "unknown")
|
|
890
|
+
tool_summary[tool_name] = tool_summary.get(tool_name, 0) + 1
|
|
902
891
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
892
|
+
summary_str = ", ".join(
|
|
893
|
+
[f"{name}: {count}" for name, count in tool_summary.items()]
|
|
894
|
+
)
|
|
895
|
+
await ui.muted(f"TOOLS USED: {summary_str}")
|
|
907
896
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
f"🚀 FINAL BATCH: Executing {len(buffered_tasks)} buffered read-only tools"
|
|
912
|
-
)
|
|
913
|
-
await ui.muted("=" * 60)
|
|
897
|
+
if i >= max_iterations:
|
|
898
|
+
if state_manager.session.show_thoughts:
|
|
899
|
+
from tunacode.ui import console as ui
|
|
914
900
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
if hasattr(part, "args") and isinstance(part.args, dict):
|
|
918
|
-
if part.tool_name == "read_file" and "file_path" in part.args:
|
|
919
|
-
tool_desc += f" → {part.args['file_path']}"
|
|
920
|
-
elif part.tool_name == "grep" and "pattern" in part.args:
|
|
921
|
-
tool_desc += f" → pattern: '{part.args['pattern']}'"
|
|
922
|
-
if "include_files" in part.args:
|
|
923
|
-
tool_desc += f", files: '{part.args['include_files']}'"
|
|
924
|
-
elif part.tool_name == "list_dir" and "directory" in part.args:
|
|
925
|
-
tool_desc += f" → {part.args['directory']}"
|
|
926
|
-
elif part.tool_name == "glob" and "pattern" in part.args:
|
|
927
|
-
tool_desc += f" → pattern: '{part.args['pattern']}'"
|
|
928
|
-
await ui.muted(tool_desc)
|
|
929
|
-
await ui.muted("=" * 60)
|
|
901
|
+
await ui.warning(f"Reached maximum iterations ({max_iterations})")
|
|
902
|
+
break
|
|
930
903
|
|
|
931
|
-
|
|
904
|
+
# Final flush: execute any remaining buffered read-only tools
|
|
905
|
+
if tool_callback and tool_buffer.has_tasks():
|
|
906
|
+
import time
|
|
932
907
|
|
|
933
|
-
|
|
934
|
-
sequential_estimate = len(buffered_tasks) * 100
|
|
935
|
-
speedup = sequential_estimate / elapsed_time if elapsed_time > 0 else 1.0
|
|
908
|
+
from tunacode.ui import console as ui
|
|
936
909
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
f"✅ Final batch completed in {elapsed_time:.0f}ms "
|
|
940
|
-
f"(~{speedup:.1f}x faster than sequential)\n"
|
|
941
|
-
)
|
|
910
|
+
buffered_tasks = tool_buffer.flush()
|
|
911
|
+
start_time = time.time()
|
|
942
912
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
tool_calls_summary = []
|
|
950
|
-
files_modified = set()
|
|
951
|
-
commands_run = []
|
|
952
|
-
|
|
953
|
-
# Analyze message history for context
|
|
954
|
-
for msg in state_manager.session.messages:
|
|
955
|
-
if hasattr(msg, "parts"):
|
|
956
|
-
for part in msg.parts:
|
|
957
|
-
if hasattr(part, "part_kind") and part.part_kind == "tool-call":
|
|
958
|
-
tool_name = getattr(part, "tool_name", "unknown")
|
|
959
|
-
tool_calls_summary.append(tool_name)
|
|
960
|
-
|
|
961
|
-
# Track specific operations
|
|
962
|
-
if tool_name in ["write_file", "update_file"] and hasattr(part, "args"):
|
|
963
|
-
if isinstance(part.args, dict) and "file_path" in part.args:
|
|
964
|
-
files_modified.add(part.args["file_path"])
|
|
965
|
-
elif tool_name in ["run_command", "bash"] and hasattr(part, "args"):
|
|
966
|
-
if isinstance(part.args, dict) and "command" in part.args:
|
|
967
|
-
commands_run.append(part.args["command"])
|
|
968
|
-
|
|
969
|
-
# Build fallback response with context
|
|
970
|
-
fallback = FallbackResponse(
|
|
971
|
-
summary="Reached maximum iterations without producing a final response.",
|
|
972
|
-
progress=f"Completed {i} iterations (limit: {max_iterations})",
|
|
973
|
-
)
|
|
913
|
+
if state_manager.session.show_thoughts:
|
|
914
|
+
await ui.muted("\n" + "=" * 60)
|
|
915
|
+
await ui.muted(
|
|
916
|
+
f"🚀 FINAL BATCH: Executing {len(buffered_tasks)} buffered read-only tools"
|
|
917
|
+
)
|
|
918
|
+
await ui.muted("=" * 60)
|
|
974
919
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
920
|
+
for idx, (part, node) in enumerate(buffered_tasks, 1):
|
|
921
|
+
tool_desc = f" [{idx}] {part.tool_name}"
|
|
922
|
+
if hasattr(part, "args") and isinstance(part.args, dict):
|
|
923
|
+
if part.tool_name == "read_file" and "file_path" in part.args:
|
|
924
|
+
tool_desc += f" → {part.args['file_path']}"
|
|
925
|
+
elif part.tool_name == "grep" and "pattern" in part.args:
|
|
926
|
+
tool_desc += f" → pattern: '{part.args['pattern']}'"
|
|
927
|
+
if "include_files" in part.args:
|
|
928
|
+
tool_desc += f", files: '{part.args['include_files']}'"
|
|
929
|
+
elif part.tool_name == "list_dir" and "directory" in part.args:
|
|
930
|
+
tool_desc += f" → {part.args['directory']}"
|
|
931
|
+
elif part.tool_name == "glob" and "pattern" in part.args:
|
|
932
|
+
tool_desc += f" → pattern: '{part.args['pattern']}'"
|
|
933
|
+
await ui.muted(tool_desc)
|
|
934
|
+
await ui.muted("=" * 60)
|
|
979
935
|
|
|
980
|
-
|
|
981
|
-
# Add what was attempted
|
|
982
|
-
if tool_calls_summary:
|
|
983
|
-
tool_counts = {}
|
|
984
|
-
for tool in tool_calls_summary:
|
|
985
|
-
tool_counts[tool] = tool_counts.get(tool, 0) + 1
|
|
986
|
-
|
|
987
|
-
fallback.issues.append(f"Executed {len(tool_calls_summary)} tool calls:")
|
|
988
|
-
for tool, count in sorted(tool_counts.items()):
|
|
989
|
-
fallback.issues.append(f" • {tool}: {count}x")
|
|
990
|
-
|
|
991
|
-
if verbosity == "detailed":
|
|
992
|
-
if files_modified:
|
|
993
|
-
fallback.issues.append(f"\nFiles modified ({len(files_modified)}):")
|
|
994
|
-
for f in sorted(files_modified)[:5]: # Limit to 5 files
|
|
995
|
-
fallback.issues.append(f" • {f}")
|
|
996
|
-
if len(files_modified) > 5:
|
|
997
|
-
fallback.issues.append(f" • ... and {len(files_modified) - 5} more")
|
|
998
|
-
|
|
999
|
-
if commands_run:
|
|
1000
|
-
fallback.issues.append(f"\nCommands executed ({len(commands_run)}):")
|
|
1001
|
-
for cmd in commands_run[:3]: # Limit to 3 commands
|
|
1002
|
-
# Truncate long commands
|
|
1003
|
-
display_cmd = cmd if len(cmd) <= 60 else cmd[:57] + "..."
|
|
1004
|
-
fallback.issues.append(f" • {display_cmd}")
|
|
1005
|
-
if len(commands_run) > 3:
|
|
1006
|
-
fallback.issues.append(f" • ... and {len(commands_run) - 3} more")
|
|
1007
|
-
|
|
1008
|
-
# Add helpful next steps
|
|
1009
|
-
fallback.next_steps.append(
|
|
1010
|
-
"The task may be too complex - try breaking it into smaller steps"
|
|
1011
|
-
)
|
|
1012
|
-
fallback.next_steps.append("Check the output above for any errors or partial progress")
|
|
1013
|
-
if files_modified:
|
|
1014
|
-
fallback.next_steps.append("Review modified files to see what changes were made")
|
|
936
|
+
await execute_tools_parallel(buffered_tasks, tool_callback)
|
|
1015
937
|
|
|
1016
|
-
|
|
1017
|
-
|
|
938
|
+
elapsed_time = (time.time() - start_time) * 1000
|
|
939
|
+
sequential_estimate = len(buffered_tasks) * 100
|
|
940
|
+
speedup = sequential_estimate / elapsed_time if elapsed_time > 0 else 1.0
|
|
1018
941
|
|
|
1019
|
-
|
|
1020
|
-
|
|
942
|
+
if state_manager.session.show_thoughts:
|
|
943
|
+
await ui.muted(
|
|
944
|
+
f"✅ Final batch completed in {elapsed_time:.0f}ms "
|
|
945
|
+
f"(~{speedup:.1f}x faster than sequential)\n"
|
|
946
|
+
)
|
|
1021
947
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
948
|
+
# If we need to add a fallback response, create a wrapper
|
|
949
|
+
if not response_state.has_user_response and i >= max_iterations and fallback_enabled:
|
|
950
|
+
patch_tool_messages("Task incomplete", state_manager=state_manager)
|
|
951
|
+
response_state.has_final_synthesis = True
|
|
952
|
+
|
|
953
|
+
# Extract context from the agent run
|
|
954
|
+
tool_calls_summary = []
|
|
955
|
+
files_modified = set()
|
|
956
|
+
commands_run = []
|
|
957
|
+
|
|
958
|
+
# Analyze message history for context
|
|
959
|
+
for msg in state_manager.session.messages:
|
|
960
|
+
if hasattr(msg, "parts"):
|
|
961
|
+
for part in msg.parts:
|
|
962
|
+
if hasattr(part, "part_kind") and part.part_kind == "tool-call":
|
|
963
|
+
tool_name = getattr(part, "tool_name", "unknown")
|
|
964
|
+
tool_calls_summary.append(tool_name)
|
|
965
|
+
|
|
966
|
+
# Track specific operations
|
|
967
|
+
if tool_name in ["write_file", "update_file"] and hasattr(
|
|
968
|
+
part, "args"
|
|
969
|
+
):
|
|
970
|
+
if isinstance(part.args, dict) and "file_path" in part.args:
|
|
971
|
+
files_modified.add(part.args["file_path"])
|
|
972
|
+
elif tool_name in ["run_command", "bash"] and hasattr(part, "args"):
|
|
973
|
+
if isinstance(part.args, dict) and "command" in part.args:
|
|
974
|
+
commands_run.append(part.args["command"])
|
|
975
|
+
|
|
976
|
+
# Build fallback response with context
|
|
977
|
+
fallback = FallbackResponse(
|
|
978
|
+
summary="Reached maximum iterations without producing a final response.",
|
|
979
|
+
progress=f"Completed {i} iterations (limit: {max_iterations})",
|
|
980
|
+
)
|
|
1025
981
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
982
|
+
# Get verbosity setting
|
|
983
|
+
verbosity = state_manager.session.user_config.get("settings", {}).get(
|
|
984
|
+
"fallback_verbosity", "normal"
|
|
985
|
+
)
|
|
1030
986
|
|
|
1031
|
-
|
|
987
|
+
if verbosity in ["normal", "detailed"]:
|
|
988
|
+
# Add what was attempted
|
|
989
|
+
if tool_calls_summary:
|
|
990
|
+
tool_counts = {}
|
|
991
|
+
for tool in tool_calls_summary:
|
|
992
|
+
tool_counts[tool] = tool_counts.get(tool, 0) + 1
|
|
993
|
+
|
|
994
|
+
fallback.issues.append(f"Executed {len(tool_calls_summary)} tool calls:")
|
|
995
|
+
for tool, count in sorted(tool_counts.items()):
|
|
996
|
+
fallback.issues.append(f" • {tool}: {count}x")
|
|
997
|
+
|
|
998
|
+
if verbosity == "detailed":
|
|
999
|
+
if files_modified:
|
|
1000
|
+
fallback.issues.append(f"\nFiles modified ({len(files_modified)}):")
|
|
1001
|
+
for f in sorted(files_modified)[:5]: # Limit to 5 files
|
|
1002
|
+
fallback.issues.append(f" • {f}")
|
|
1003
|
+
if len(files_modified) > 5:
|
|
1004
|
+
fallback.issues.append(
|
|
1005
|
+
f" • ... and {len(files_modified) - 5} more"
|
|
1006
|
+
)
|
|
1007
|
+
|
|
1008
|
+
if commands_run:
|
|
1009
|
+
fallback.issues.append(f"\nCommands executed ({len(commands_run)}):")
|
|
1010
|
+
for cmd in commands_run[:3]: # Limit to 3 commands
|
|
1011
|
+
# Truncate long commands
|
|
1012
|
+
display_cmd = cmd if len(cmd) <= 60 else cmd[:57] + "..."
|
|
1013
|
+
fallback.issues.append(f" • {display_cmd}")
|
|
1014
|
+
if len(commands_run) > 3:
|
|
1015
|
+
fallback.issues.append(f" • ... and {len(commands_run) - 3} more")
|
|
1016
|
+
|
|
1017
|
+
# Add helpful next steps
|
|
1018
|
+
fallback.next_steps.append(
|
|
1019
|
+
"The task may be too complex - try breaking it into smaller steps"
|
|
1020
|
+
)
|
|
1021
|
+
fallback.next_steps.append(
|
|
1022
|
+
"Check the output above for any errors or partial progress"
|
|
1023
|
+
)
|
|
1024
|
+
if files_modified:
|
|
1025
|
+
fallback.next_steps.append(
|
|
1026
|
+
"Review modified files to see what changes were made"
|
|
1027
|
+
)
|
|
1032
1028
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1029
|
+
# Create comprehensive output
|
|
1030
|
+
output_parts = [fallback.summary, ""]
|
|
1031
|
+
|
|
1032
|
+
if fallback.progress:
|
|
1033
|
+
output_parts.append(f"Progress: {fallback.progress}")
|
|
1034
|
+
|
|
1035
|
+
if fallback.issues:
|
|
1036
|
+
output_parts.append("\nWhat happened:")
|
|
1037
|
+
output_parts.extend(fallback.issues)
|
|
1038
|
+
|
|
1039
|
+
if fallback.next_steps:
|
|
1040
|
+
output_parts.append("\nSuggested next steps:")
|
|
1041
|
+
for step in fallback.next_steps:
|
|
1042
|
+
output_parts.append(f" • {step}")
|
|
1043
|
+
|
|
1044
|
+
comprehensive_output = "\n".join(output_parts)
|
|
1045
|
+
|
|
1046
|
+
# Create a wrapper object that mimics AgentRun with the required attributes
|
|
1047
|
+
class AgentRunWrapper:
|
|
1048
|
+
def __init__(self, wrapped_run, fallback_result):
|
|
1049
|
+
self._wrapped = wrapped_run
|
|
1050
|
+
self._result = fallback_result
|
|
1051
|
+
self.response_state = response_state
|
|
1052
|
+
|
|
1053
|
+
def __getattribute__(self, name):
|
|
1054
|
+
# Handle special attributes first to avoid conflicts
|
|
1055
|
+
if name in ["_wrapped", "_result", "response_state"]:
|
|
1056
|
+
return object.__getattribute__(self, name)
|
|
1057
|
+
|
|
1058
|
+
# Explicitly handle 'result' to return our fallback result
|
|
1059
|
+
if name == "result":
|
|
1060
|
+
return object.__getattribute__(self, "_result")
|
|
1061
|
+
|
|
1062
|
+
# Delegate all other attributes to the wrapped object
|
|
1063
|
+
try:
|
|
1064
|
+
return getattr(object.__getattribute__(self, "_wrapped"), name)
|
|
1065
|
+
except AttributeError:
|
|
1066
|
+
raise AttributeError(
|
|
1067
|
+
f"'{type(self).__name__}' object has no attribute '{name}'"
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
return AgentRunWrapper(agent_run, SimpleResult(comprehensive_output))
|
|
1071
|
+
|
|
1072
|
+
# For non-fallback cases, we still need to handle the response_state
|
|
1073
|
+
# Create a minimal wrapper just to add response_state
|
|
1074
|
+
class AgentRunWithState:
|
|
1075
|
+
def __init__(self, wrapped_run):
|
|
1036
1076
|
self._wrapped = wrapped_run
|
|
1037
|
-
self._result = fallback_result
|
|
1038
1077
|
self.response_state = response_state
|
|
1039
1078
|
|
|
1040
1079
|
def __getattribute__(self, name):
|
|
1041
|
-
# Handle special attributes first
|
|
1042
|
-
if name in ["_wrapped", "
|
|
1080
|
+
# Handle special attributes first
|
|
1081
|
+
if name in ["_wrapped", "response_state"]:
|
|
1043
1082
|
return object.__getattribute__(self, name)
|
|
1044
1083
|
|
|
1045
|
-
# Explicitly handle 'result' to return our fallback result
|
|
1046
|
-
if name == "result":
|
|
1047
|
-
return object.__getattribute__(self, "_result")
|
|
1048
|
-
|
|
1049
1084
|
# Delegate all other attributes to the wrapped object
|
|
1050
1085
|
try:
|
|
1051
1086
|
return getattr(object.__getattribute__(self, "_wrapped"), name)
|
|
@@ -1054,26 +1089,6 @@ async def process_request(
|
|
|
1054
1089
|
f"'{type(self).__name__}' object has no attribute '{name}'"
|
|
1055
1090
|
)
|
|
1056
1091
|
|
|
1057
|
-
return
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
# Create a minimal wrapper just to add response_state
|
|
1061
|
-
class AgentRunWithState:
|
|
1062
|
-
def __init__(self, wrapped_run):
|
|
1063
|
-
self._wrapped = wrapped_run
|
|
1064
|
-
self.response_state = response_state
|
|
1065
|
-
|
|
1066
|
-
def __getattribute__(self, name):
|
|
1067
|
-
# Handle special attributes first
|
|
1068
|
-
if name in ["_wrapped", "response_state"]:
|
|
1069
|
-
return object.__getattribute__(self, name)
|
|
1070
|
-
|
|
1071
|
-
# Delegate all other attributes to the wrapped object
|
|
1072
|
-
try:
|
|
1073
|
-
return getattr(object.__getattribute__(self, "_wrapped"), name)
|
|
1074
|
-
except AttributeError:
|
|
1075
|
-
raise AttributeError(
|
|
1076
|
-
f"'{type(self).__name__}' object has no attribute '{name}'"
|
|
1077
|
-
)
|
|
1078
|
-
|
|
1079
|
-
return AgentRunWithState(agent_run)
|
|
1092
|
+
return AgentRunWithState(agent_run)
|
|
1093
|
+
except asyncio.CancelledError:
|
|
1094
|
+
raise UserAbortError("User aborted the request.")
|
|
@@ -49,6 +49,14 @@ class CostCalculator:
|
|
|
49
49
|
|
|
50
50
|
pricing = model_config.pricing
|
|
51
51
|
|
|
52
|
+
# Safety check for None pricing
|
|
53
|
+
if not pricing:
|
|
54
|
+
return 0.0
|
|
55
|
+
|
|
56
|
+
# Safety check for None pricing attributes
|
|
57
|
+
if pricing.input is None or pricing.output is None:
|
|
58
|
+
return 0.0
|
|
59
|
+
|
|
52
60
|
input_cost = (prompt_tokens / TOKENS_PER_MILLION) * pricing.input
|
|
53
61
|
|
|
54
62
|
output_cost = (completion_tokens / TOKENS_PER_MILLION) * pricing.output
|
|
@@ -60,18 +60,27 @@ class UsageTracker(UsageTrackerProtocol):
|
|
|
60
60
|
if not api_model_name.startswith(provider_prefix + ":"):
|
|
61
61
|
final_model_name = f"{provider_prefix}:{api_model_name}"
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
cost = self.calculator.calculate_cost(
|
|
64
64
|
prompt_tokens=parsed_data.get("prompt_tokens", 0),
|
|
65
65
|
completion_tokens=parsed_data.get("completion_tokens", 0),
|
|
66
66
|
model_name=final_model_name,
|
|
67
67
|
)
|
|
68
68
|
|
|
69
|
+
# Ensure cost is never None
|
|
70
|
+
return cost if cost is not None else 0.0
|
|
71
|
+
|
|
69
72
|
def _update_state(self, parsed_data: dict, cost: float):
|
|
70
73
|
"""Updates the last_call and session_total usage in the state."""
|
|
71
74
|
session = self.state_manager.session
|
|
72
75
|
prompt_tokens = parsed_data.get("prompt_tokens", 0)
|
|
73
76
|
completion_tokens = parsed_data.get("completion_tokens", 0)
|
|
74
77
|
|
|
78
|
+
# Initialize usage dicts if they're None
|
|
79
|
+
if session.last_call_usage is None:
|
|
80
|
+
session.last_call_usage = {"prompt_tokens": 0, "completion_tokens": 0, "cost": 0.0}
|
|
81
|
+
if session.session_total_usage is None:
|
|
82
|
+
session.session_total_usage = {"prompt_tokens": 0, "completion_tokens": 0, "cost": 0.0}
|
|
83
|
+
|
|
75
84
|
# Update last call usage
|
|
76
85
|
session.last_call_usage["prompt_tokens"] = prompt_tokens
|
|
77
86
|
session.last_call_usage["completion_tokens"] = completion_tokens
|
|
@@ -85,6 +94,13 @@ class UsageTracker(UsageTrackerProtocol):
|
|
|
85
94
|
async def _display_summary(self):
|
|
86
95
|
"""Formats and prints the usage summary to the console."""
|
|
87
96
|
session = self.state_manager.session
|
|
97
|
+
|
|
98
|
+
# Initialize usage dicts if they're None
|
|
99
|
+
if session.last_call_usage is None:
|
|
100
|
+
session.last_call_usage = {"prompt_tokens": 0, "completion_tokens": 0, "cost": 0.0}
|
|
101
|
+
if session.session_total_usage is None:
|
|
102
|
+
session.session_total_usage = {"prompt_tokens": 0, "completion_tokens": 0, "cost": 0.0}
|
|
103
|
+
|
|
88
104
|
prompt = session.last_call_usage["prompt_tokens"]
|
|
89
105
|
completion = session.last_call_usage["completion_tokens"]
|
|
90
106
|
last_cost = session.last_call_usage["cost"]
|
tunacode/ui/input.py
CHANGED
|
@@ -75,12 +75,13 @@ async def multiline_input(
|
|
|
75
75
|
state_manager: Optional[StateManager] = None, command_registry=None
|
|
76
76
|
) -> str:
|
|
77
77
|
"""Get multiline input from the user with @file completion and highlighting."""
|
|
78
|
-
kb = create_key_bindings()
|
|
78
|
+
kb = create_key_bindings(state_manager)
|
|
79
79
|
placeholder = formatted_text(
|
|
80
80
|
(
|
|
81
81
|
"<darkgrey>"
|
|
82
82
|
"<bold>Enter</bold> to submit • "
|
|
83
83
|
"<bold>Esc + Enter</bold> for new line • "
|
|
84
|
+
"<bold>Esc</bold> to cancel • "
|
|
84
85
|
"<bold>/help</bold> for commands"
|
|
85
86
|
"</darkgrey>"
|
|
86
87
|
)
|
tunacode/ui/keybindings.py
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
"""Key binding handlers for TunaCode UI."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
4
|
+
|
|
3
5
|
from prompt_toolkit.key_binding import KeyBindings
|
|
4
6
|
|
|
7
|
+
from ..core.state import StateManager
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
5
10
|
|
|
6
|
-
|
|
11
|
+
|
|
12
|
+
def create_key_bindings(state_manager: StateManager = None) -> KeyBindings:
|
|
7
13
|
"""Create and configure key bindings for the UI."""
|
|
8
14
|
kb = KeyBindings()
|
|
9
15
|
|
|
@@ -22,4 +28,14 @@ def create_key_bindings() -> KeyBindings:
|
|
|
22
28
|
"""Insert a newline when escape then enter is pressed."""
|
|
23
29
|
event.current_buffer.insert_text("\n")
|
|
24
30
|
|
|
31
|
+
@kb.add("escape")
|
|
32
|
+
def _escape(event):
|
|
33
|
+
"""Immediately interrupts the current operation."""
|
|
34
|
+
current_task = state_manager.session.current_task if state_manager else None
|
|
35
|
+
if current_task and not current_task.done():
|
|
36
|
+
logger.debug("Interrupting current task")
|
|
37
|
+
current_task.cancel()
|
|
38
|
+
else:
|
|
39
|
+
logger.debug("Escape key pressed outside task context")
|
|
40
|
+
|
|
25
41
|
return kb
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tunacode-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.47
|
|
4
4
|
Summary: Your agentic CLI developer.
|
|
5
5
|
Author-email: larock22 <noreply@github.com>
|
|
6
|
-
License
|
|
7
|
-
Project-URL: Homepage, https://
|
|
8
|
-
Project-URL: Repository, https://github.com/
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://tunacode.xyz/
|
|
8
|
+
Project-URL: Repository, https://github.com/alchemiststudiosDOTai/tunacode
|
|
9
|
+
Project-URL: Issues, https://github.com/alchemiststudiosDOTai/tunacode/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/alchemiststudiosDOTai/tunacode#readme
|
|
9
11
|
Keywords: cli,agent,development,automation
|
|
10
12
|
Classifier: Development Status :: 4 - Beta
|
|
11
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -16,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
16
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
19
|
Classifier: Topic :: Software Development
|
|
18
20
|
Classifier: Topic :: Utilities
|
|
19
|
-
Requires-Python:
|
|
21
|
+
Requires-Python: <3.14,>=3.10
|
|
20
22
|
Description-Content-Type: text/markdown
|
|
21
23
|
License-File: LICENSE
|
|
22
24
|
Requires-Dist: typer==0.15.3
|
|
@@ -46,7 +48,7 @@ Dynamic: license-file
|
|
|
46
48
|
|
|
47
49
|
**AI-powered CLI coding assistant**
|
|
48
50
|
|
|
49
|
-

|
|
50
52
|
|
|
51
53
|
</div>
|
|
52
54
|
|
|
@@ -62,6 +64,29 @@ wget -qO- https://raw.githubusercontent.com/alchemiststudiosDOTai/tunacode/maste
|
|
|
62
64
|
pip install tunacode-cli
|
|
63
65
|
```
|
|
64
66
|
|
|
67
|
+
## Development Installation
|
|
68
|
+
|
|
69
|
+
For contributors and developers who want to work on TunaCode:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Clone the repository
|
|
73
|
+
git clone https://github.com/alchemiststudiosDOTai/tunacode.git
|
|
74
|
+
cd tunacode
|
|
75
|
+
|
|
76
|
+
# Quick setup (recommended)
|
|
77
|
+
./scripts/setup_dev_env.sh
|
|
78
|
+
|
|
79
|
+
# Or manual setup
|
|
80
|
+
python3 -m venv venv
|
|
81
|
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
82
|
+
pip install -e ".[dev]"
|
|
83
|
+
|
|
84
|
+
# Verify installation
|
|
85
|
+
python -m tunacode --version
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
See [Development Guide](docs/DEVELOPMENT.md) for detailed instructions.
|
|
89
|
+
|
|
65
90
|
## Configuration
|
|
66
91
|
|
|
67
92
|
Choose your AI provider and set your API key:
|
|
@@ -139,6 +164,7 @@ _Note: While the tool is fully functional, we're focusing on stability and core
|
|
|
139
164
|
- [**Advanced Configuration**](docs/ADVANCED-CONFIG.md) - Provider setup, MCP, customization
|
|
140
165
|
- [**Architecture**](docs/ARCHITECTURE.md) - Source code organization and design
|
|
141
166
|
- [**Development**](docs/DEVELOPMENT.md) - Contributing and development setup
|
|
167
|
+
- [**Troubleshooting**](docs/TROUBLESHOOTING.md) - Common issues and solutions
|
|
142
168
|
|
|
143
169
|
## Links
|
|
144
170
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
tunacode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
tunacode/constants.py,sha256=
|
|
2
|
+
tunacode/constants.py,sha256=833fgfAmkcJmU1RmSYDhGCOzoU-eUMdgbcfBBwUQOZ4,5168
|
|
3
3
|
tunacode/context.py,sha256=_gXVCyjU052jlyRAl9tklZSwl5U_zI_EIX8XN87VVWE,2786
|
|
4
4
|
tunacode/exceptions.py,sha256=oDO1SVKOgjcKIylwqdbqh_g5my4roU5mB9Nv4n_Vb0s,3877
|
|
5
5
|
tunacode/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -7,7 +7,7 @@ tunacode/setup.py,sha256=XPt4eAK-qcIZQv64jGZ_ryxcImDwps9OmXjJfIS1xcs,1899
|
|
|
7
7
|
tunacode/types.py,sha256=Czq7jYXHq7fZQtyqkCN5_7eEu1wyYUcB50C6v3sTWDw,8188
|
|
8
8
|
tunacode/cli/__init__.py,sha256=zgs0UbAck8hfvhYsWhWOfBe5oK09ug2De1r4RuQZREA,55
|
|
9
9
|
tunacode/cli/main.py,sha256=erP6jNXcxVQOVn8sm6uNaLEAYevniVXl6Sem872mW68,2755
|
|
10
|
-
tunacode/cli/repl.py,sha256=
|
|
10
|
+
tunacode/cli/repl.py,sha256=rSsUievdOdTkxzHNIEgUHbuWbk0ki78743Wz5hVFe9s,18917
|
|
11
11
|
tunacode/cli/commands/__init__.py,sha256=zmE9JcJ9Qd2xJhgdS4jMDJOoZsrAZmL5MAFxbKkk7F8,1670
|
|
12
12
|
tunacode/cli/commands/base.py,sha256=GxUuDsDSpz0iXryy8MrEw88UM3C3yxL__kDK1QhshoA,2517
|
|
13
13
|
tunacode/cli/commands/registry.py,sha256=XVuLpp5S4Fw7GfIZfLrVZFo4jMLMNmYNpYN7xWgXyOk,8223
|
|
@@ -27,7 +27,7 @@ tunacode/core/code_index.py,sha256=jgAx3lSWP_DwnyiP5Jkm1YvX4JJyI4teMzlNrJSpEOA,1
|
|
|
27
27
|
tunacode/core/state.py,sha256=2lsDgq0uIIc_MnXPE9SG_1fYFBDWWlgqgqm2Ik1iFBs,5599
|
|
28
28
|
tunacode/core/tool_handler.py,sha256=BPjR013OOO0cLXPdLeL2FDK0ixUwOYu59FfHdcdFhp4,2277
|
|
29
29
|
tunacode/core/agents/__init__.py,sha256=UUJiPYb91arwziSpjd7vIk7XNGA_4HQbsOIbskSqevA,149
|
|
30
|
-
tunacode/core/agents/main.py,sha256=
|
|
30
|
+
tunacode/core/agents/main.py,sha256=BRy3joJnn4GpUW47PyhiKK1JLMSamlK7wDMLwAGzSG0,47441
|
|
31
31
|
tunacode/core/agents/utils.py,sha256=7kJAiUlkyWO3-b4T07XsGgycVrcNhv3NEPLdaktBnP4,12847
|
|
32
32
|
tunacode/core/background/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
tunacode/core/background/manager.py,sha256=rJdl3eDLTQwjbT7VhxXcJbZopCNR3M8ZGMbmeVnwwMc,1126
|
|
@@ -46,8 +46,8 @@ tunacode/core/setup/coordinator.py,sha256=oVTN2xIeJERXitVJpkIk9tDGLs1D1bxIRmaogJ
|
|
|
46
46
|
tunacode/core/setup/environment_setup.py,sha256=n3IrObKEynHZSwtUJ1FddMg2C4sHz7ca42awemImV8s,2225
|
|
47
47
|
tunacode/core/setup/git_safety_setup.py,sha256=CRIqrQt0QUJQRS344njty_iCqTorrDhHlXRuET7w0Tk,6714
|
|
48
48
|
tunacode/core/token_usage/api_response_parser.py,sha256=CTtqGaFaxpkzkW3TEbe00QJzyRULpWN1EQxIYMleseg,1622
|
|
49
|
-
tunacode/core/token_usage/cost_calculator.py,sha256=
|
|
50
|
-
tunacode/core/token_usage/usage_tracker.py,sha256=
|
|
49
|
+
tunacode/core/token_usage/cost_calculator.py,sha256=RjO-O0JENBuGOrWP7QgBZlZxeXC-PAIr8tj_9p_BxOU,2058
|
|
50
|
+
tunacode/core/token_usage/usage_tracker.py,sha256=kuAjUCyQkFykPy5mqsLRbKhZW298pyiCuFGn-ptBpy4,4657
|
|
51
51
|
tunacode/prompts/system.md,sha256=hXpjZ8Yiv2Acr2_6EmC2uOklP8FbmvyYR9oais-1KLk,16290
|
|
52
52
|
tunacode/services/__init__.py,sha256=w_E8QK6RnvKSvU866eDe8BCRV26rAm4d3R-Yg06OWCU,19
|
|
53
53
|
tunacode/services/mcp.py,sha256=R48X73KQjQ9vwhBrtbWHSBl-4K99QXmbIhh5J_1Gezo,3046
|
|
@@ -68,8 +68,8 @@ tunacode/ui/completers.py,sha256=Jx1zyCESwdm_4ZopvCBtb0bCJF-bRy8aBWG2yhPQtDc,487
|
|
|
68
68
|
tunacode/ui/console.py,sha256=icb7uYrV8XmZg9glreEy5MrvDkmrKxbf_ZkNqElN1uE,2120
|
|
69
69
|
tunacode/ui/constants.py,sha256=A76B_KpM8jCuBYRg4cPmhi8_j6LLyWttO7_jjv47r3w,421
|
|
70
70
|
tunacode/ui/decorators.py,sha256=e2KM-_pI5EKHa2M045IjUe4rPkTboxaKHXJT0K3461g,1914
|
|
71
|
-
tunacode/ui/input.py,sha256=
|
|
72
|
-
tunacode/ui/keybindings.py,sha256=
|
|
71
|
+
tunacode/ui/input.py,sha256=x_7G9VVdvydpEk2kcyG-OBKND5lL5ADbiGcDXF1n5UA,3014
|
|
72
|
+
tunacode/ui/keybindings.py,sha256=tn0q0eRw72j8xPWX0673Xc-vmwlvFEyuhy3C451ntfE,1233
|
|
73
73
|
tunacode/ui/lexers.py,sha256=tmg4ic1enyTRLzanN5QPP7D_0n12YjX_8ZhsffzhXA4,1340
|
|
74
74
|
tunacode/ui/output.py,sha256=51O0VHajte4dXHK5Az5SSP4IOb2q5SbCwvqdAoxyg7c,5665
|
|
75
75
|
tunacode/ui/panels.py,sha256=dBZEVIJqliWreY-hz6HpW5rdBmPOJZ6sPrv8KQ3eNhk,8570
|
|
@@ -91,9 +91,9 @@ tunacode/utils/system.py,sha256=FSoibTIH0eybs4oNzbYyufIiV6gb77QaeY2yGqW39AY,1138
|
|
|
91
91
|
tunacode/utils/text_utils.py,sha256=6YBD9QfkDO44-6jxnwRWIpmfIifPG-NqMzy_O2NAouc,7277
|
|
92
92
|
tunacode/utils/token_counter.py,sha256=lLbkrNUraRQn5RMhwnGurqq1RHFDyn4AaFhruONWIxo,2690
|
|
93
93
|
tunacode/utils/user_configuration.py,sha256=Ilz8dpGVJDBE2iLWHAPT0xR8D51VRKV3kIbsAz8Bboc,3275
|
|
94
|
-
tunacode_cli-0.0.
|
|
95
|
-
tunacode_cli-0.0.
|
|
96
|
-
tunacode_cli-0.0.
|
|
97
|
-
tunacode_cli-0.0.
|
|
98
|
-
tunacode_cli-0.0.
|
|
99
|
-
tunacode_cli-0.0.
|
|
94
|
+
tunacode_cli-0.0.47.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
|
|
95
|
+
tunacode_cli-0.0.47.dist-info/METADATA,sha256=oHfTMPtEUgbgVd7Nm0bb39wAKAuPKRHskBpZmDnBxMM,5902
|
|
96
|
+
tunacode_cli-0.0.47.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
97
|
+
tunacode_cli-0.0.47.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
|
|
98
|
+
tunacode_cli-0.0.47.dist-info/top_level.txt,sha256=lKy2P6BWNi5XSA4DHFvyjQ14V26lDZctwdmhEJrxQbU,9
|
|
99
|
+
tunacode_cli-0.0.47.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|