ripperdoc 0.1.0__py3-none-any.whl → 0.2.0__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.
- ripperdoc/cli/cli.py +9 -7
- ripperdoc/cli/commands/agents_cmd.py +1 -1
- ripperdoc/cli/commands/context_cmd.py +2 -2
- ripperdoc/cli/commands/cost_cmd.py +1 -1
- ripperdoc/cli/commands/resume_cmd.py +3 -3
- ripperdoc/cli/commands/status_cmd.py +5 -5
- ripperdoc/cli/commands/tasks_cmd.py +5 -5
- ripperdoc/cli/ui/context_display.py +4 -3
- ripperdoc/cli/ui/rich_ui.py +49 -34
- ripperdoc/cli/ui/spinner.py +3 -4
- ripperdoc/core/agents.py +6 -4
- ripperdoc/core/default_tools.py +10 -4
- ripperdoc/core/query.py +9 -7
- ripperdoc/core/tool.py +1 -1
- ripperdoc/sdk/client.py +1 -1
- ripperdoc/tools/bash_tool.py +4 -4
- ripperdoc/tools/file_edit_tool.py +2 -2
- ripperdoc/tools/file_read_tool.py +2 -2
- ripperdoc/tools/file_write_tool.py +8 -2
- ripperdoc/tools/glob_tool.py +2 -2
- ripperdoc/tools/grep_tool.py +2 -2
- ripperdoc/tools/ls_tool.py +3 -3
- ripperdoc/tools/mcp_tools.py +15 -9
- ripperdoc/tools/multi_edit_tool.py +2 -2
- ripperdoc/tools/notebook_edit_tool.py +3 -3
- ripperdoc/tools/task_tool.py +13 -5
- ripperdoc/tools/todo_tool.py +4 -4
- ripperdoc/tools/tool_search_tool.py +6 -4
- ripperdoc/utils/mcp.py +12 -4
- ripperdoc/utils/message_compaction.py +25 -9
- {ripperdoc-0.1.0.dist-info → ripperdoc-0.2.0.dist-info}/METADATA +1 -1
- {ripperdoc-0.1.0.dist-info → ripperdoc-0.2.0.dist-info}/RECORD +36 -36
- {ripperdoc-0.1.0.dist-info → ripperdoc-0.2.0.dist-info}/WHEEL +0 -0
- {ripperdoc-0.1.0.dist-info → ripperdoc-0.2.0.dist-info}/entry_points.txt +0 -0
- {ripperdoc-0.1.0.dist-info → ripperdoc-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {ripperdoc-0.1.0.dist-info → ripperdoc-0.2.0.dist-info}/top_level.txt +0 -0
ripperdoc/tools/bash_tool.py
CHANGED
|
@@ -134,11 +134,11 @@ build projects, run tests, and interact with the file system."""
|
|
|
134
134
|
return [
|
|
135
135
|
ToolUseExample(
|
|
136
136
|
description="Run a read-only listing in sandboxed mode",
|
|
137
|
-
|
|
137
|
+
example={"command": "ls -la", "sandbox": True, "timeout": 10000},
|
|
138
138
|
),
|
|
139
139
|
ToolUseExample(
|
|
140
140
|
description="Start a long task in the background with a timeout",
|
|
141
|
-
|
|
141
|
+
example={
|
|
142
142
|
"command": "npm test",
|
|
143
143
|
"run_in_background": True,
|
|
144
144
|
"timeout": 600000,
|
|
@@ -395,10 +395,10 @@ build projects, run tests, and interact with the file system."""
|
|
|
395
395
|
if output.duration_ms:
|
|
396
396
|
timing = f" ({format_duration(output.duration_ms)}"
|
|
397
397
|
if output.timeout_ms:
|
|
398
|
-
timing += f" / timeout {output.timeout_ms/1000:.0f}s"
|
|
398
|
+
timing += f" / timeout {output.timeout_ms / 1000:.0f}s"
|
|
399
399
|
timing += ")"
|
|
400
400
|
elif output.timeout_ms:
|
|
401
|
-
timing = f" (timeout {output.timeout_ms/1000:.0f}s)"
|
|
401
|
+
timing = f" (timeout {output.timeout_ms / 1000:.0f}s)"
|
|
402
402
|
|
|
403
403
|
result_parts.append(f"{exit_code_text}{timing}")
|
|
404
404
|
|
|
@@ -60,7 +60,7 @@ match exactly (including whitespace and indentation)."""
|
|
|
60
60
|
return [
|
|
61
61
|
ToolUseExample(
|
|
62
62
|
description="Rename a function definition once",
|
|
63
|
-
|
|
63
|
+
example={
|
|
64
64
|
"file_path": "/repo/src/app.py",
|
|
65
65
|
"old_string": "def old_name(",
|
|
66
66
|
"new_string": "def new_name(",
|
|
@@ -69,7 +69,7 @@ match exactly (including whitespace and indentation)."""
|
|
|
69
69
|
),
|
|
70
70
|
ToolUseExample(
|
|
71
71
|
description="Replace every occurrence of a constant across a file",
|
|
72
|
-
|
|
72
|
+
example={
|
|
73
73
|
"file_path": "/repo/src/config.ts",
|
|
74
74
|
"old_string": 'API_BASE = "http://localhost"',
|
|
75
75
|
"new_string": 'API_BASE = "https://api.example.com"',
|
|
@@ -56,11 +56,11 @@ and limit to read only a portion of the file."""
|
|
|
56
56
|
return [
|
|
57
57
|
ToolUseExample(
|
|
58
58
|
description="Read the top of a file to understand structure",
|
|
59
|
-
|
|
59
|
+
example={"file_path": "/repo/src/main.py", "limit": 50},
|
|
60
60
|
),
|
|
61
61
|
ToolUseExample(
|
|
62
62
|
description="Inspect a slice of a large log without loading everything",
|
|
63
|
-
|
|
63
|
+
example={"file_path": "/repo/logs/server.log", "offset": 200, "limit": 40},
|
|
64
64
|
),
|
|
65
65
|
]
|
|
66
66
|
|
|
@@ -53,11 +53,17 @@ the file if it already exists."""
|
|
|
53
53
|
return [
|
|
54
54
|
ToolUseExample(
|
|
55
55
|
description="Create a JSON fixture file",
|
|
56
|
-
|
|
56
|
+
example={
|
|
57
|
+
"file_path": "/repo/tests/fixtures/sample.json",
|
|
58
|
+
"content": '{\n "items": []\n}\n',
|
|
59
|
+
},
|
|
57
60
|
),
|
|
58
61
|
ToolUseExample(
|
|
59
62
|
description="Write a short markdown note",
|
|
60
|
-
|
|
63
|
+
example={
|
|
64
|
+
"file_path": "/repo/docs/USAGE.md",
|
|
65
|
+
"content": "# Usage\n\nRun `make test`.\n",
|
|
66
|
+
},
|
|
61
67
|
),
|
|
62
68
|
]
|
|
63
69
|
|
ripperdoc/tools/glob_tool.py
CHANGED
|
@@ -62,11 +62,11 @@ class GlobTool(Tool[GlobToolInput, GlobToolOutput]):
|
|
|
62
62
|
return [
|
|
63
63
|
ToolUseExample(
|
|
64
64
|
description="Find Python sources inside src",
|
|
65
|
-
|
|
65
|
+
example={"pattern": "src/**/*.py"},
|
|
66
66
|
),
|
|
67
67
|
ToolUseExample(
|
|
68
68
|
description="Locate snapshot files within tests",
|
|
69
|
-
|
|
69
|
+
example={"pattern": "tests/**/__snapshots__/*.snap", "path": "/repo"},
|
|
70
70
|
),
|
|
71
71
|
]
|
|
72
72
|
|
ripperdoc/tools/grep_tool.py
CHANGED
|
@@ -80,11 +80,11 @@ class GrepTool(Tool[GrepToolInput, GrepToolOutput]):
|
|
|
80
80
|
return [
|
|
81
81
|
ToolUseExample(
|
|
82
82
|
description="Find TODO comments in TypeScript files",
|
|
83
|
-
|
|
83
|
+
example={"pattern": "TODO", "glob": "**/*.ts", "output_mode": "content"},
|
|
84
84
|
),
|
|
85
85
|
ToolUseExample(
|
|
86
86
|
description="List files referencing a function name",
|
|
87
|
-
|
|
87
|
+
example={
|
|
88
88
|
"pattern": "fetchUserData",
|
|
89
89
|
"output_mode": "files_with_matches",
|
|
90
90
|
"path": "/repo/src",
|
ripperdoc/tools/ls_tool.py
CHANGED
|
@@ -224,11 +224,11 @@ class LSTool(Tool[LSToolInput, LSToolOutput]):
|
|
|
224
224
|
return [
|
|
225
225
|
ToolUseExample(
|
|
226
226
|
description="List the repository root with defaults",
|
|
227
|
-
|
|
227
|
+
example={"path": "/repo"},
|
|
228
228
|
),
|
|
229
229
|
ToolUseExample(
|
|
230
230
|
description="Inspect a package while skipping build outputs",
|
|
231
|
-
|
|
231
|
+
example={"path": "/repo/packages/api", "ignore": ["dist/**", "node_modules/**"]},
|
|
232
232
|
),
|
|
233
233
|
]
|
|
234
234
|
|
|
@@ -270,7 +270,7 @@ class LSTool(Tool[LSToolInput, LSToolOutput]):
|
|
|
270
270
|
def render_tool_use_message(self, input_data: LSToolInput, verbose: bool = False) -> str:
|
|
271
271
|
ignore_display = ""
|
|
272
272
|
if input_data.ignore:
|
|
273
|
-
ignore_display = f
|
|
273
|
+
ignore_display = f', ignore: "{", ".join(input_data.ignore)}"'
|
|
274
274
|
return f'path: "{input_data.path}"{ignore_display}'
|
|
275
275
|
|
|
276
276
|
async def call(
|
ripperdoc/tools/mcp_tools.py
CHANGED
|
@@ -34,7 +34,7 @@ from ripperdoc.utils.mcp import (
|
|
|
34
34
|
try:
|
|
35
35
|
import mcp.types as mcp_types # type: ignore
|
|
36
36
|
except Exception: # pragma: no cover - SDK may be missing at runtime
|
|
37
|
-
mcp_types = None
|
|
37
|
+
mcp_types = None # type: ignore[assignment]
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
logger = get_logger()
|
|
@@ -467,7 +467,11 @@ class ReadMcpResourceTool(Tool[ReadMcpResourceInput, ReadMcpResourceOutput]):
|
|
|
467
467
|
|
|
468
468
|
if session and mcp_types:
|
|
469
469
|
try:
|
|
470
|
-
|
|
470
|
+
# Convert string to AnyUrl
|
|
471
|
+
from mcp.types import AnyUrl
|
|
472
|
+
|
|
473
|
+
uri = AnyUrl(input_data.uri)
|
|
474
|
+
result = await session.read_resource(uri)
|
|
471
475
|
for item in result.contents:
|
|
472
476
|
if isinstance(item, mcp_types.TextResourceContents):
|
|
473
477
|
parts.append(
|
|
@@ -535,12 +539,12 @@ class ReadMcpResourceTool(Tool[ReadMcpResourceInput, ReadMcpResourceOutput]):
|
|
|
535
539
|
)
|
|
536
540
|
)
|
|
537
541
|
|
|
538
|
-
|
|
542
|
+
read_result: Any = ReadMcpResourceOutput(
|
|
539
543
|
server=input_data.server, uri=input_data.uri, content=content_text, contents=parts
|
|
540
544
|
)
|
|
541
545
|
yield ToolResult(
|
|
542
|
-
data=
|
|
543
|
-
result_for_assistant=self.render_result_for_assistant(
|
|
546
|
+
data=read_result,
|
|
547
|
+
result_for_assistant=self.render_result_for_assistant(read_result), # type: ignore[arg-type]
|
|
544
548
|
)
|
|
545
549
|
|
|
546
550
|
|
|
@@ -676,14 +680,16 @@ class DynamicMcpTool(Tool[BaseModel, McpToolCallOutput]):
|
|
|
676
680
|
|
|
677
681
|
try:
|
|
678
682
|
args = input_data.model_dump(exclude_none=True)
|
|
679
|
-
|
|
683
|
+
call_result = await session.call_tool(
|
|
680
684
|
self.tool_info.name,
|
|
681
685
|
args or {},
|
|
682
686
|
)
|
|
683
|
-
raw_blocks = getattr(
|
|
687
|
+
raw_blocks = getattr(call_result, "content", None)
|
|
684
688
|
content_blocks = _normalize_content_blocks(raw_blocks)
|
|
685
689
|
content_text = _render_content_blocks(content_blocks) if content_blocks else None
|
|
686
|
-
structured =
|
|
690
|
+
structured = (
|
|
691
|
+
call_result.structuredContent if hasattr(call_result, "structuredContent") else None
|
|
692
|
+
)
|
|
687
693
|
assistant_text = content_text
|
|
688
694
|
if structured:
|
|
689
695
|
assistant_text = (assistant_text + "\n" if assistant_text else "") + json.dumps(
|
|
@@ -696,7 +702,7 @@ class DynamicMcpTool(Tool[BaseModel, McpToolCallOutput]):
|
|
|
696
702
|
text=content_text,
|
|
697
703
|
content_blocks=content_blocks,
|
|
698
704
|
structured_content=structured,
|
|
699
|
-
is_error=getattr(
|
|
705
|
+
is_error=getattr(call_result, "isError", False),
|
|
700
706
|
)
|
|
701
707
|
yield ToolResult(
|
|
702
708
|
data=output,
|
|
@@ -124,7 +124,7 @@ class MultiEditTool(Tool[MultiEditToolInput, MultiEditToolOutput]):
|
|
|
124
124
|
return [
|
|
125
125
|
ToolUseExample(
|
|
126
126
|
description="Apply multiple replacements in one pass",
|
|
127
|
-
|
|
127
|
+
example={
|
|
128
128
|
"file_path": "/repo/src/app.py",
|
|
129
129
|
"edits": [
|
|
130
130
|
{"old_string": "DEBUG = True", "new_string": "DEBUG = False"},
|
|
@@ -134,7 +134,7 @@ class MultiEditTool(Tool[MultiEditToolInput, MultiEditToolOutput]):
|
|
|
134
134
|
),
|
|
135
135
|
ToolUseExample(
|
|
136
136
|
description="Create a new file then adjust content",
|
|
137
|
-
|
|
137
|
+
example={
|
|
138
138
|
"file_path": "/repo/docs/notes.txt",
|
|
139
139
|
"edits": [
|
|
140
140
|
{"old_string": "", "new_string": "Line one\nLine two\n"},
|
|
@@ -101,7 +101,7 @@ class NotebookEditTool(Tool[NotebookEditInput, NotebookEditOutput]):
|
|
|
101
101
|
return [
|
|
102
102
|
ToolUseExample(
|
|
103
103
|
description="Replace a markdown cell by id",
|
|
104
|
-
|
|
104
|
+
example={
|
|
105
105
|
"notebook_path": "/repo/notebooks/analysis.ipynb",
|
|
106
106
|
"cell_id": "abc123",
|
|
107
107
|
"new_source": "# Updated overview\\nThis notebook analyzes revenue.",
|
|
@@ -111,7 +111,7 @@ class NotebookEditTool(Tool[NotebookEditInput, NotebookEditOutput]):
|
|
|
111
111
|
),
|
|
112
112
|
ToolUseExample(
|
|
113
113
|
description="Insert a new code cell at the beginning",
|
|
114
|
-
|
|
114
|
+
example={
|
|
115
115
|
"notebook_path": "/repo/notebooks/analysis.ipynb",
|
|
116
116
|
"cell_type": "code",
|
|
117
117
|
"edit_mode": "insert",
|
|
@@ -246,7 +246,7 @@ class NotebookEditTool(Tool[NotebookEditInput, NotebookEditOutput]):
|
|
|
246
246
|
"id": new_id,
|
|
247
247
|
"source": new_source,
|
|
248
248
|
"metadata": {},
|
|
249
|
-
"execution_count":
|
|
249
|
+
"execution_count": None, # type: ignore[dict-item]
|
|
250
250
|
"outputs": [],
|
|
251
251
|
}
|
|
252
252
|
)
|
ripperdoc/tools/task_tool.py
CHANGED
|
@@ -85,7 +85,7 @@ class TaskTool(Tool[TaskToolInput, TaskToolOutput]):
|
|
|
85
85
|
details: List[str] = []
|
|
86
86
|
if output.tool_use_count:
|
|
87
87
|
details.append(f"{output.tool_use_count} tool uses")
|
|
88
|
-
details.append(f"{output.duration_ms/1000:.1f}s")
|
|
88
|
+
details.append(f"{output.duration_ms / 1000:.1f}s")
|
|
89
89
|
if output.missing_tools:
|
|
90
90
|
details.append(f"missing tools: {', '.join(output.missing_tools)}")
|
|
91
91
|
|
|
@@ -125,11 +125,18 @@ class TaskTool(Tool[TaskToolInput, TaskToolOutput]):
|
|
|
125
125
|
f"Missing or unknown tools: {', '.join(missing_tools) if missing_tools else 'none'}"
|
|
126
126
|
)
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
# Type conversion: List[object] -> List[Tool[Any, Any]]
|
|
129
|
+
from ripperdoc.core.tool import Tool
|
|
130
|
+
|
|
131
|
+
typed_agent_tools: List[Tool[Any, Any]] = [
|
|
132
|
+
tool for tool in agent_tools if isinstance(tool, Tool)
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
agent_system_prompt = self._build_agent_prompt(target_agent, typed_agent_tools)
|
|
129
136
|
subagent_messages = [create_user_message(input_data.prompt)]
|
|
130
137
|
|
|
131
138
|
subagent_context = QueryContext(
|
|
132
|
-
tools=
|
|
139
|
+
tools=typed_agent_tools,
|
|
133
140
|
safe_mode=context.safe_mode,
|
|
134
141
|
verbose=context.verbose,
|
|
135
142
|
model=target_agent.model or "task",
|
|
@@ -142,7 +149,7 @@ class TaskTool(Tool[TaskToolInput, TaskToolOutput]):
|
|
|
142
149
|
yield ToolProgress(content=f"Launching subagent '{target_agent.agent_type}'")
|
|
143
150
|
|
|
144
151
|
async for message in query(
|
|
145
|
-
subagent_messages,
|
|
152
|
+
subagent_messages, # type: ignore[arg-type]
|
|
146
153
|
agent_system_prompt,
|
|
147
154
|
{},
|
|
148
155
|
subagent_context,
|
|
@@ -183,7 +190,8 @@ class TaskTool(Tool[TaskToolInput, TaskToolOutput]):
|
|
|
183
190
|
)
|
|
184
191
|
yield ToolProgress(content=f"Subagent: {short}")
|
|
185
192
|
assistant_messages.append(message) # type: ignore[arg-type]
|
|
186
|
-
|
|
193
|
+
if isinstance(message, AssistantMessage):
|
|
194
|
+
tool_use_count += self._count_tool_uses(message)
|
|
187
195
|
|
|
188
196
|
duration_ms = (time.time() - start) * 1000
|
|
189
197
|
result_text = (
|
ripperdoc/tools/todo_tool.py
CHANGED
|
@@ -140,7 +140,7 @@ class TodoWriteTool(Tool[TodoWriteToolInput, TodoToolOutput]):
|
|
|
140
140
|
return [
|
|
141
141
|
ToolUseExample(
|
|
142
142
|
description="Seed a three-step plan",
|
|
143
|
-
|
|
143
|
+
example={
|
|
144
144
|
"todos": [
|
|
145
145
|
{
|
|
146
146
|
"id": "plan",
|
|
@@ -165,7 +165,7 @@ class TodoWriteTool(Tool[TodoWriteToolInput, TodoToolOutput]):
|
|
|
165
165
|
),
|
|
166
166
|
ToolUseExample(
|
|
167
167
|
description="Update a single task already in progress",
|
|
168
|
-
|
|
168
|
+
example={
|
|
169
169
|
"todos": [
|
|
170
170
|
{
|
|
171
171
|
"id": "bugfix-123",
|
|
@@ -263,11 +263,11 @@ class TodoReadTool(Tool[TodoReadToolInput, TodoToolOutput]):
|
|
|
263
263
|
return [
|
|
264
264
|
ToolUseExample(
|
|
265
265
|
description="Get only the next actionable todo",
|
|
266
|
-
|
|
266
|
+
example={"next_only": True},
|
|
267
267
|
),
|
|
268
268
|
ToolUseExample(
|
|
269
269
|
description="List recent completed tasks with a limit",
|
|
270
|
-
|
|
270
|
+
example={"status": ["completed"], "limit": 5},
|
|
271
271
|
),
|
|
272
272
|
]
|
|
273
273
|
|
|
@@ -94,11 +94,11 @@ class ToolSearchTool(Tool[ToolSearchInput, ToolSearchOutput]):
|
|
|
94
94
|
return [
|
|
95
95
|
ToolUseExample(
|
|
96
96
|
description="Search for notebook-related tools and activate top results",
|
|
97
|
-
|
|
97
|
+
example={"query": "notebook", "max_results": 3},
|
|
98
98
|
),
|
|
99
99
|
ToolUseExample(
|
|
100
100
|
description="Activate a known tool by name",
|
|
101
|
-
|
|
101
|
+
example={"names": ["mcp__search__query"], "auto_activate": True},
|
|
102
102
|
),
|
|
103
103
|
]
|
|
104
104
|
|
|
@@ -119,7 +119,9 @@ class ToolSearchTool(Tool[ToolSearchInput, ToolSearchOutput]):
|
|
|
119
119
|
return False
|
|
120
120
|
|
|
121
121
|
async def validate_input(
|
|
122
|
-
self,
|
|
122
|
+
self,
|
|
123
|
+
input_data: ToolSearchInput,
|
|
124
|
+
context: Optional[ToolUseContext] = None, # noqa: ARG002
|
|
123
125
|
) -> ValidationResult:
|
|
124
126
|
if not (input_data.query or input_data.names):
|
|
125
127
|
return ValidationResult(
|
|
@@ -193,7 +195,7 @@ class ToolSearchTool(Tool[ToolSearchInput, ToolSearchOutput]):
|
|
|
193
195
|
|
|
194
196
|
avg_len = sum(doc_len for _, _, _, doc_len, _ in corpus) / len(corpus)
|
|
195
197
|
query_terms = _tokenize(normalized)
|
|
196
|
-
df = defaultdict(int)
|
|
198
|
+
df: Dict[str, int] = defaultdict(int)
|
|
197
199
|
for _, _, tokens, _, _ in corpus:
|
|
198
200
|
seen_terms = set(tokens)
|
|
199
201
|
for term in query_terms:
|
ripperdoc/utils/mcp.py
CHANGED
|
@@ -72,7 +72,10 @@ def _load_json_file(path: Path) -> Dict[str, Any]:
|
|
|
72
72
|
if not path.exists():
|
|
73
73
|
return {}
|
|
74
74
|
try:
|
|
75
|
-
|
|
75
|
+
data = json.loads(path.read_text())
|
|
76
|
+
if isinstance(data, dict):
|
|
77
|
+
return data
|
|
78
|
+
return {}
|
|
76
79
|
except (OSError, json.JSONDecodeError) as exc:
|
|
77
80
|
logger.error(f"Failed to load JSON from {path}: {exc}")
|
|
78
81
|
return {}
|
|
@@ -181,11 +184,11 @@ class McpRuntime:
|
|
|
181
184
|
self.servers.append(await self._connect_server(config))
|
|
182
185
|
return self.servers
|
|
183
186
|
|
|
184
|
-
async def _list_roots_callback(self, *_: Any, **__: Any):
|
|
187
|
+
async def _list_roots_callback(self, *_: Any, **__: Any) -> Optional[Any]:
|
|
185
188
|
if not mcp_types:
|
|
186
189
|
return None
|
|
187
190
|
return mcp_types.ListRootsResult(
|
|
188
|
-
roots=[mcp_types.Root(uri=Path(self.project_path).resolve().as_uri())]
|
|
191
|
+
roots=[mcp_types.Root(uri=Path(self.project_path).resolve().as_uri())] # type: ignore[arg-type]
|
|
189
192
|
)
|
|
190
193
|
|
|
191
194
|
async def _connect_server(self, config: McpServerInfo) -> McpServerInfo:
|
|
@@ -228,17 +231,22 @@ class McpRuntime:
|
|
|
228
231
|
stdio_client(stdio_params)
|
|
229
232
|
)
|
|
230
233
|
|
|
234
|
+
if read_stream is None or write_stream is None:
|
|
235
|
+
raise ValueError("Failed to create read/write streams for MCP server")
|
|
236
|
+
|
|
231
237
|
session = await self._exit_stack.enter_async_context(
|
|
232
238
|
ClientSession(
|
|
233
239
|
read_stream,
|
|
234
240
|
write_stream,
|
|
235
|
-
list_roots_callback=self._list_roots_callback,
|
|
241
|
+
list_roots_callback=self._list_roots_callback, # type: ignore[arg-type]
|
|
236
242
|
client_info=mcp_types.Implementation(name="ripperdoc", version=__version__),
|
|
237
243
|
)
|
|
238
244
|
)
|
|
239
245
|
|
|
240
246
|
init_result = await session.initialize()
|
|
241
247
|
capabilities = session.get_server_capabilities()
|
|
248
|
+
if capabilities is None:
|
|
249
|
+
capabilities = mcp_types.ServerCapabilities()
|
|
242
250
|
|
|
243
251
|
info.status = "connected"
|
|
244
252
|
info.instructions = init_result.instructions or info.instructions
|
|
@@ -281,7 +281,9 @@ def get_remaining_context_tokens(
|
|
|
281
281
|
"""Return the context window minus the model's configured output tokens."""
|
|
282
282
|
context_limit = max(get_model_context_limit(model_profile, explicit_limit), MIN_CONTEXT_TOKENS)
|
|
283
283
|
try:
|
|
284
|
-
max_output_tokens =
|
|
284
|
+
max_output_tokens = (
|
|
285
|
+
int(getattr(model_profile, "max_tokens", 0) or 0) if model_profile else 0
|
|
286
|
+
)
|
|
285
287
|
except (TypeError, ValueError):
|
|
286
288
|
max_output_tokens = 0
|
|
287
289
|
return max(MIN_CONTEXT_TOKENS, context_limit - max(0, max_output_tokens))
|
|
@@ -304,11 +306,15 @@ def get_context_usage_status(
|
|
|
304
306
|
"""Compute context usage thresholds following claude-code semantics."""
|
|
305
307
|
context_limit = max(max_context_tokens or DEFAULT_CONTEXT_TOKENS, MIN_CONTEXT_TOKENS)
|
|
306
308
|
effective_limit = (
|
|
307
|
-
max(MIN_CONTEXT_TOKENS, context_limit - AUTO_COMPACT_BUFFER)
|
|
309
|
+
max(MIN_CONTEXT_TOKENS, context_limit - AUTO_COMPACT_BUFFER)
|
|
310
|
+
if auto_compact_enabled
|
|
311
|
+
else context_limit
|
|
308
312
|
)
|
|
309
313
|
|
|
310
314
|
tokens_left = max(effective_limit - used_tokens, 0)
|
|
311
|
-
percent_left =
|
|
315
|
+
percent_left = (
|
|
316
|
+
0.0 if effective_limit <= 0 else min(100.0, (tokens_left / effective_limit) * 100)
|
|
317
|
+
)
|
|
312
318
|
percent_used = 100.0 - percent_left
|
|
313
319
|
|
|
314
320
|
warning_limit = max(0, effective_limit - WARNING_THRESHOLD)
|
|
@@ -451,7 +457,9 @@ def _estimate_message_tokens(content_block: Any) -> int:
|
|
|
451
457
|
if isinstance(content, list):
|
|
452
458
|
total = 0
|
|
453
459
|
for part in content:
|
|
454
|
-
part_type = getattr(part, "type", None) or (
|
|
460
|
+
part_type = getattr(part, "type", None) or (
|
|
461
|
+
part.get("type") if isinstance(part, dict) else None
|
|
462
|
+
)
|
|
455
463
|
if part_type == "text":
|
|
456
464
|
text_val = getattr(part, "text", None) if hasattr(part, "text") else None
|
|
457
465
|
if text_val is None and isinstance(part, dict):
|
|
@@ -531,7 +539,9 @@ def compact_messages(
|
|
|
531
539
|
token_counts_by_tool_use_id[tool_use_id] = token_count
|
|
532
540
|
|
|
533
541
|
latest_tool_use_ids = (
|
|
534
|
-
tool_use_ids_to_compact[-MAX_TOOL_USES_TO_PRESERVE:]
|
|
542
|
+
tool_use_ids_to_compact[-MAX_TOOL_USES_TO_PRESERVE:]
|
|
543
|
+
if MAX_TOOL_USES_TO_PRESERVE > 0
|
|
544
|
+
else []
|
|
535
545
|
)
|
|
536
546
|
total_token_count = sum(token_counts_by_tool_use_id.values())
|
|
537
547
|
|
|
@@ -571,7 +581,7 @@ def compact_messages(
|
|
|
571
581
|
compacted_messages.append(message)
|
|
572
582
|
continue
|
|
573
583
|
|
|
574
|
-
if msg_type == "assistant":
|
|
584
|
+
if msg_type == "assistant" and isinstance(message, AssistantMessage):
|
|
575
585
|
# Copy content list to avoid mutating the original message.
|
|
576
586
|
compacted_messages.append(
|
|
577
587
|
AssistantMessage(
|
|
@@ -597,7 +607,11 @@ def compact_messages(
|
|
|
597
607
|
new_block = content_item.model_copy()
|
|
598
608
|
new_block.text = COMPACT_PLACEHOLDER
|
|
599
609
|
else:
|
|
600
|
-
block_dict =
|
|
610
|
+
block_dict = (
|
|
611
|
+
dict(content_item)
|
|
612
|
+
if isinstance(content_item, dict)
|
|
613
|
+
else {"type": "tool_result"}
|
|
614
|
+
)
|
|
601
615
|
block_dict["text"] = COMPACT_PLACEHOLDER
|
|
602
616
|
block_dict["tool_use_id"] = tool_use_id
|
|
603
617
|
new_block = MessageContent(**block_dict)
|
|
@@ -608,9 +622,11 @@ def compact_messages(
|
|
|
608
622
|
elif isinstance(content_item, dict):
|
|
609
623
|
filtered_content.append(MessageContent(**content_item))
|
|
610
624
|
else:
|
|
611
|
-
filtered_content.append(
|
|
625
|
+
filtered_content.append(
|
|
626
|
+
MessageContent(type=str(block_type or "text"), text=str(content_item))
|
|
627
|
+
)
|
|
612
628
|
|
|
613
|
-
if modified:
|
|
629
|
+
if modified and isinstance(message, UserMessage):
|
|
614
630
|
compacted_messages.append(
|
|
615
631
|
UserMessage(
|
|
616
632
|
message=message.message.model_copy(update={"content": filtered_content}),
|
|
@@ -1,65 +1,65 @@
|
|
|
1
1
|
ripperdoc/__init__.py,sha256=8uE0Wb2dqhd3j7g6jm1ObxZ2cn3yf5E3SoVpLzYcFJs,66
|
|
2
2
|
ripperdoc/__main__.py,sha256=7oIFEXI2irIoZ_dhcMd3hCs4Dj8tmMBbwiVACAoeE-k,506
|
|
3
3
|
ripperdoc/cli/__init__.py,sha256=03wf6gXBcEgXJrDJS-W_5BEG_DdJ_ep7CxQFPML-73g,35
|
|
4
|
-
ripperdoc/cli/cli.py,sha256=
|
|
4
|
+
ripperdoc/cli/cli.py,sha256=ldVNPsZIEdwaNwbrwbgAZMfQFQ1tEXw5uz26fP7avGc,10928
|
|
5
5
|
ripperdoc/cli/commands/__init__.py,sha256=2ymRdOj3EekyIioOxhJNMZyjWaSNVixNZZ6DONfTXtc,2189
|
|
6
|
-
ripperdoc/cli/commands/agents_cmd.py,sha256=
|
|
6
|
+
ripperdoc/cli/commands/agents_cmd.py,sha256=_4Cf1onhtgVrT4PnVPqHLueiAEbJcM9VP-_usPs77xE,9277
|
|
7
7
|
ripperdoc/cli/commands/base.py,sha256=4KUjxCM04MwbSMUKVNEBph_jeAKPI8b5MHsUFoz7l5g,386
|
|
8
8
|
ripperdoc/cli/commands/clear_cmd.py,sha256=zSYT0Nn_htZzLWTTQ4E5KWHfRg0Q5CYvRO4e--7thBY,345
|
|
9
9
|
ripperdoc/cli/commands/compact_cmd.py,sha256=C_qdPTPdg1cOHdmODkaYoRusosgiqRK6c6KID_Gwq0k,330
|
|
10
10
|
ripperdoc/cli/commands/config_cmd.py,sha256=ebIQk7zUFv353liWfbBSSfPiOaaCR7rQsd_eTw7nsvY,884
|
|
11
|
-
ripperdoc/cli/commands/context_cmd.py,sha256=
|
|
12
|
-
ripperdoc/cli/commands/cost_cmd.py,sha256=
|
|
11
|
+
ripperdoc/cli/commands/context_cmd.py,sha256=8PpTi4b0-tcxuxf4Qh59HOyH4eGmGe8TV7T_KrS_9yw,4007
|
|
12
|
+
ripperdoc/cli/commands/cost_cmd.py,sha256=AoGRDDu48qsMGuS5zgScg_YdNqkwoj33D2PTh-1uZ34,2637
|
|
13
13
|
ripperdoc/cli/commands/exit_cmd.py,sha256=B0CNKQos2eRC4LSjizLdKsFYzFfwRkrUur6Afu3Fh9M,334
|
|
14
14
|
ripperdoc/cli/commands/help_cmd.py,sha256=iz1vR-rmWsvvfzdebLiIWEWrcMZo5_Eb55_wLr4Ufno,508
|
|
15
15
|
ripperdoc/cli/commands/mcp_cmd.py,sha256=S0iQxclxqgbIxbKcC9oFrckLalzk-1eyAYwkfEZQsGU,2307
|
|
16
16
|
ripperdoc/cli/commands/models_cmd.py,sha256=fQOwb_5_vNPNKF2NHepvLrsE25qTETPC9zlkvnrRjxs,11915
|
|
17
|
-
ripperdoc/cli/commands/resume_cmd.py,sha256=
|
|
18
|
-
ripperdoc/cli/commands/status_cmd.py,sha256=
|
|
19
|
-
ripperdoc/cli/commands/tasks_cmd.py,sha256=
|
|
17
|
+
ripperdoc/cli/commands/resume_cmd.py,sha256=F99haT29dUHYlgrIYKk2cadSOTrwkLOEs_D2RotNZXI,2977
|
|
18
|
+
ripperdoc/cli/commands/status_cmd.py,sha256=rAhHksegqdLGy551Hh22oVwR6ZSfkDvAPOjNiNQJ9_s,5494
|
|
19
|
+
ripperdoc/cli/commands/tasks_cmd.py,sha256=oCGEdfjCqPSHJOAWGQUABl7X5ltYJWtRPB69b8d6m4w,7286
|
|
20
20
|
ripperdoc/cli/commands/todos_cmd.py,sha256=7Q0B1NVqGtB3R29ndbn4m0VQQm-YQ7d4Wlk7vJ7dLQI,1848
|
|
21
21
|
ripperdoc/cli/commands/tools_cmd.py,sha256=3cMi0vN4mAUhpKqJtRgNvZfcKzRPaMs_pkYYXlyvSSU,384
|
|
22
22
|
ripperdoc/cli/ui/__init__.py,sha256=TxSzTYdITlrYmYVfins_w_jzPqqWRpqky5u1ikwvmtM,43
|
|
23
|
-
ripperdoc/cli/ui/context_display.py,sha256=
|
|
23
|
+
ripperdoc/cli/ui/context_display.py,sha256=3ezdtHVwltkPQ5etYwfqUh-fjnpPu8B3P81UzrdHxZs,10020
|
|
24
24
|
ripperdoc/cli/ui/helpers.py,sha256=TJCipP0neh-96ETQfGhusCJ4aWt5gLw1HZbI-3bWDpw,739
|
|
25
|
-
ripperdoc/cli/ui/rich_ui.py,sha256=
|
|
26
|
-
ripperdoc/cli/ui/spinner.py,sha256=
|
|
25
|
+
ripperdoc/cli/ui/rich_ui.py,sha256=jBTcJc5hTXEgeih7CtJUqo6sB7nVwEg3NGZ4DvJI9QA,43907
|
|
26
|
+
ripperdoc/cli/ui/spinner.py,sha256=XsPRwJ-70InLX9Qw50CEgSHn5oKA5PFIue8Un4edhUk,1449
|
|
27
27
|
ripperdoc/core/__init__.py,sha256=UemJCA-Y8df1466AX-YbRFj071zKajmqO1mi40YVW2g,40
|
|
28
|
-
ripperdoc/core/agents.py,sha256=
|
|
28
|
+
ripperdoc/core/agents.py,sha256=sLgMeoDYXciBszRLCdbVCZRfxzXjlwGpWcx8qFk1Xm4,10240
|
|
29
29
|
ripperdoc/core/commands.py,sha256=NXCkljYbAP4dDoRy-_3semFNWxG4YAk9q82u8FTKH60,835
|
|
30
30
|
ripperdoc/core/config.py,sha256=gxR0uOhX11nv9fUQpcnQ1fHRtUEpt6v1GHSItyYxQYA,13240
|
|
31
|
-
ripperdoc/core/default_tools.py,sha256=
|
|
31
|
+
ripperdoc/core/default_tools.py,sha256=PpZ49yjDzV5vtSkYq_quxY8u-nk5uJPp25RfMa9wZtA,2086
|
|
32
32
|
ripperdoc/core/permissions.py,sha256=lEKAUX9ksQxOFrPUkwwQe_3PMAr2xKgGBwjTOz8a8hM,8398
|
|
33
|
-
ripperdoc/core/query.py,sha256=
|
|
33
|
+
ripperdoc/core/query.py,sha256=f3sfzM9vSZJGbCUMg2wmT7iESCgFrNv2DqGYL38mJK0,26329
|
|
34
34
|
ripperdoc/core/system_prompt.py,sha256=OG_GUm9E30FnvL4p2L3_1A7oG54V0Kw5xRJPRzJrx2o,24066
|
|
35
|
-
ripperdoc/core/tool.py,sha256=
|
|
35
|
+
ripperdoc/core/tool.py,sha256=GbmQSVc8XQXw4aDVwFpYGZV3OFbU5wIz7n3xNWlNeyI,6446
|
|
36
36
|
ripperdoc/sdk/__init__.py,sha256=aDSgI4lcCDs9cV3bxNmEEV3SuYx2aCd4VnUjs6H-R7E,213
|
|
37
|
-
ripperdoc/sdk/client.py,sha256=
|
|
37
|
+
ripperdoc/sdk/client.py,sha256=21f8viIh3BhSjXcI_MvgzfAcy7r289Thyhc_qgnQrB0,10556
|
|
38
38
|
ripperdoc/tools/__init__.py,sha256=RBFz0DDnztDXMqv_zRxFHVY-ez2HYcncx8zh_y-BX6w,42
|
|
39
39
|
ripperdoc/tools/background_shell.py,sha256=zdea03hpSGHEsVreyApn2bWMLzMQYo5ZmaHDp_Q11SA,9235
|
|
40
40
|
ripperdoc/tools/bash_output_tool.py,sha256=ljIOzTOnkbQfe3jExlhpUlMiLT6HpeD-1QI-D1CwHh8,3379
|
|
41
|
-
ripperdoc/tools/bash_tool.py,sha256=
|
|
42
|
-
ripperdoc/tools/file_edit_tool.py,sha256=
|
|
43
|
-
ripperdoc/tools/file_read_tool.py,sha256=
|
|
44
|
-
ripperdoc/tools/file_write_tool.py,sha256=
|
|
45
|
-
ripperdoc/tools/glob_tool.py,sha256=
|
|
46
|
-
ripperdoc/tools/grep_tool.py,sha256=
|
|
41
|
+
ripperdoc/tools/bash_tool.py,sha256=Ww92WIybH_5fqCJTEyR6JcIL6OQgj283uqvkFa1paqU,36071
|
|
42
|
+
ripperdoc/tools/file_edit_tool.py,sha256=zJib_Lqh9MlgPbNat9-LK1TTt8GsAXIuBEusuGWw8v8,11153
|
|
43
|
+
ripperdoc/tools/file_read_tool.py,sha256=EKA-OvDgRRzIMeK4t5ZFavB5ZTk0TzOJwxg-vD5ggho,5868
|
|
44
|
+
ripperdoc/tools/file_write_tool.py,sha256=X4b8PDJszRtp_B6daCsUsQAwUc-4PsNbXpm3FUG5sR0,4569
|
|
45
|
+
ripperdoc/tools/glob_tool.py,sha256=WxTz-cOz-whMxbQGCLpNmXfLgnxH9dh-1EFh11id64Q,4309
|
|
46
|
+
ripperdoc/tools/grep_tool.py,sha256=KyR5abvzy780evWu6tuMv9v6xGyGrTlLliJHan1N3S4,8098
|
|
47
47
|
ripperdoc/tools/kill_bash_tool.py,sha256=36F8w2Rm1IVQitwOAwS-D8NTnyQdWfKWIam44qlXErk,4625
|
|
48
|
-
ripperdoc/tools/ls_tool.py,sha256=
|
|
49
|
-
ripperdoc/tools/mcp_tools.py,sha256=
|
|
50
|
-
ripperdoc/tools/multi_edit_tool.py,sha256=
|
|
51
|
-
ripperdoc/tools/notebook_edit_tool.py,sha256=
|
|
52
|
-
ripperdoc/tools/task_tool.py,sha256=
|
|
53
|
-
ripperdoc/tools/todo_tool.py,sha256=
|
|
54
|
-
ripperdoc/tools/tool_search_tool.py,sha256=
|
|
48
|
+
ripperdoc/tools/ls_tool.py,sha256=H9pYsZKwx7j7Ow98eTx67KNKFbNO0yY-JUX34lAuhy8,8928
|
|
49
|
+
ripperdoc/tools/mcp_tools.py,sha256=msGNVKx8aDoLhs7Xvta1XMdLNP78W8Pi_CqLhwIjKiM,30309
|
|
50
|
+
ripperdoc/tools/multi_edit_tool.py,sha256=EpkCsjVTXTa4fOfETABjXNoKvQ6LRK8Xnv_t22N_Mcg,14968
|
|
51
|
+
ripperdoc/tools/notebook_edit_tool.py,sha256=y7_NuB7NrmUu-KkRQfkSegHRuiyPYdGxpjD9pFb_f5M,12064
|
|
52
|
+
ripperdoc/tools/task_tool.py,sha256=qp8jYLy8VMriny4fqmobYXh1dac2UyCBSAXZNk4iI0w,11739
|
|
53
|
+
ripperdoc/tools/todo_tool.py,sha256=aB0ejA_np3zLWTNUmC7yEVlx46tTtT_WgHEsqrbXIn4,12855
|
|
54
|
+
ripperdoc/tools/tool_search_tool.py,sha256=l3DU2UuCPU-XSvFDsdIrl0VkdwtR_E5dMk0idAP2p2w,13333
|
|
55
55
|
ripperdoc/utils/__init__.py,sha256=gdso60znB2hsYZ_YZBKVcuOY3QVfoqD2wHQ4pvr5lSw,37
|
|
56
56
|
ripperdoc/utils/bash_constants.py,sha256=KNn8bzB6nVU5jid9jvjiH4FAu8pP3DZONJ-OknJypAQ,1641
|
|
57
57
|
ripperdoc/utils/bash_output_utils.py,sha256=3Cf5wKJzRbUesmCNy5HtXIBtv0Z2BxklTfFHJ9q1T3w,1210
|
|
58
58
|
ripperdoc/utils/exit_code_handlers.py,sha256=QtO1iDxVAb8Xp03D6_QixPoJC-RQlcp3ssIo_rm4two,7973
|
|
59
59
|
ripperdoc/utils/log.py,sha256=QoqIxTctArJ3L5dwL6v4AZkMTGQozhDlNdM5BAn3QL8,2361
|
|
60
|
-
ripperdoc/utils/mcp.py,sha256=
|
|
60
|
+
ripperdoc/utils/mcp.py,sha256=w2yHygSWNUKyM4AU2xZmfEMyfN3oZAHuswtEAD0v1Dc,14652
|
|
61
61
|
ripperdoc/utils/memory.py,sha256=5Shv8O8VjlUz0JHbIYm6oPLP5GAzzhJ3ZJIf3-GHXnE,7336
|
|
62
|
-
ripperdoc/utils/message_compaction.py,sha256=
|
|
62
|
+
ripperdoc/utils/message_compaction.py,sha256=NVyDU58wsFuW85XWemKJqvtA1BMR-J8qCx10BIYKwnw,24112
|
|
63
63
|
ripperdoc/utils/messages.py,sha256=1vAYabUXbuywYQETH7GStQGehPnkxrZ7ZkzeXbv4JC4,14725
|
|
64
64
|
ripperdoc/utils/output_utils.py,sha256=9vROdxdZ_8FO1I_OxuTuTeUPWcJoWy9laTUFzTCDths,6162
|
|
65
65
|
ripperdoc/utils/path_utils.py,sha256=C45Q3OeXnj-0FVEtvf_tdG5922XB6HthUzlUCvfc17Y,1626
|
|
@@ -73,9 +73,9 @@ ripperdoc/utils/permissions/__init__.py,sha256=-1aKvRC05kuvLacbeu7w1W5ANamOTAho4
|
|
|
73
73
|
ripperdoc/utils/permissions/path_validation_utils.py,sha256=0saGffF-IwCHc_GrDimU8Vz_SAczJb_Ui29ea_hkj4E,5482
|
|
74
74
|
ripperdoc/utils/permissions/shell_command_validation.py,sha256=BkK-wEwAuJPoC4XIx2Zj6MF3gXcWReozIwigXgib0z0,2446
|
|
75
75
|
ripperdoc/utils/permissions/tool_permission_utils.py,sha256=6Fdu9-dMKhLsUExjEjoS0EUeRpEVN5UkqyseIC05YmM,9207
|
|
76
|
-
ripperdoc-0.
|
|
77
|
-
ripperdoc-0.
|
|
78
|
-
ripperdoc-0.
|
|
79
|
-
ripperdoc-0.
|
|
80
|
-
ripperdoc-0.
|
|
81
|
-
ripperdoc-0.
|
|
76
|
+
ripperdoc-0.2.0.dist-info/licenses/LICENSE,sha256=bRv9UhBor6GhnQDj12RciDcRfu0R7sB-lqCy1sWF75c,9242
|
|
77
|
+
ripperdoc-0.2.0.dist-info/METADATA,sha256=dwxYrx56ASM7ZbKyF5n6ZfZq9DwGfHU8sJWnOVCTY7M,5317
|
|
78
|
+
ripperdoc-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
79
|
+
ripperdoc-0.2.0.dist-info/entry_points.txt,sha256=79aohFxFPJmrQ3-Mhain04vb3EWpuc0EyzvDDUnwAu4,81
|
|
80
|
+
ripperdoc-0.2.0.dist-info/top_level.txt,sha256=u8LbdTr1a-laHgCO0Utl_R3QGFUhLxWelCDnP2ZgpCU,10
|
|
81
|
+
ripperdoc-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|