ripperdoc 0.2.10__py3-none-any.whl → 0.3.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/__init__.py +1 -1
- ripperdoc/cli/cli.py +164 -57
- ripperdoc/cli/commands/__init__.py +4 -0
- ripperdoc/cli/commands/agents_cmd.py +3 -7
- ripperdoc/cli/commands/doctor_cmd.py +29 -0
- ripperdoc/cli/commands/memory_cmd.py +2 -1
- ripperdoc/cli/commands/models_cmd.py +61 -5
- ripperdoc/cli/commands/resume_cmd.py +1 -0
- ripperdoc/cli/commands/skills_cmd.py +103 -0
- ripperdoc/cli/commands/stats_cmd.py +4 -4
- ripperdoc/cli/commands/status_cmd.py +10 -0
- ripperdoc/cli/commands/tasks_cmd.py +6 -3
- ripperdoc/cli/commands/themes_cmd.py +139 -0
- ripperdoc/cli/ui/file_mention_completer.py +63 -13
- ripperdoc/cli/ui/helpers.py +6 -3
- ripperdoc/cli/ui/interrupt_handler.py +34 -0
- ripperdoc/cli/ui/panels.py +13 -8
- ripperdoc/cli/ui/rich_ui.py +451 -32
- ripperdoc/cli/ui/spinner.py +68 -5
- ripperdoc/cli/ui/tool_renderers.py +10 -9
- ripperdoc/cli/ui/wizard.py +18 -11
- ripperdoc/core/agents.py +4 -0
- ripperdoc/core/config.py +235 -0
- ripperdoc/core/default_tools.py +1 -0
- ripperdoc/core/hooks/llm_callback.py +0 -1
- ripperdoc/core/hooks/manager.py +6 -0
- ripperdoc/core/permissions.py +82 -5
- ripperdoc/core/providers/openai.py +55 -9
- ripperdoc/core/query.py +349 -108
- ripperdoc/core/query_utils.py +17 -14
- ripperdoc/core/skills.py +1 -0
- ripperdoc/core/theme.py +298 -0
- ripperdoc/core/tool.py +8 -3
- ripperdoc/protocol/__init__.py +14 -0
- ripperdoc/protocol/models.py +300 -0
- ripperdoc/protocol/stdio.py +1453 -0
- ripperdoc/tools/background_shell.py +49 -5
- ripperdoc/tools/bash_tool.py +75 -9
- ripperdoc/tools/file_edit_tool.py +98 -29
- ripperdoc/tools/file_read_tool.py +139 -8
- ripperdoc/tools/file_write_tool.py +46 -3
- ripperdoc/tools/grep_tool.py +98 -8
- ripperdoc/tools/lsp_tool.py +9 -15
- ripperdoc/tools/multi_edit_tool.py +26 -3
- ripperdoc/tools/skill_tool.py +52 -1
- ripperdoc/tools/task_tool.py +33 -8
- ripperdoc/utils/file_watch.py +12 -6
- ripperdoc/utils/image_utils.py +125 -0
- ripperdoc/utils/log.py +30 -3
- ripperdoc/utils/lsp.py +9 -3
- ripperdoc/utils/mcp.py +80 -18
- ripperdoc/utils/message_formatting.py +2 -2
- ripperdoc/utils/messages.py +177 -32
- ripperdoc/utils/pending_messages.py +50 -0
- ripperdoc/utils/permissions/shell_command_validation.py +3 -3
- ripperdoc/utils/permissions/tool_permission_utils.py +9 -3
- ripperdoc/utils/platform.py +198 -0
- ripperdoc/utils/session_heatmap.py +1 -3
- ripperdoc/utils/session_history.py +2 -2
- ripperdoc/utils/session_stats.py +1 -0
- ripperdoc/utils/shell_utils.py +8 -5
- ripperdoc/utils/todo.py +0 -6
- {ripperdoc-0.2.10.dist-info → ripperdoc-0.3.0.dist-info}/METADATA +49 -17
- {ripperdoc-0.2.10.dist-info → ripperdoc-0.3.0.dist-info}/RECORD +68 -61
- {ripperdoc-0.2.10.dist-info → ripperdoc-0.3.0.dist-info}/WHEEL +1 -1
- ripperdoc/sdk/__init__.py +0 -9
- ripperdoc/sdk/client.py +0 -408
- {ripperdoc-0.2.10.dist-info → ripperdoc-0.3.0.dist-info}/entry_points.txt +0 -0
- {ripperdoc-0.2.10.dist-info → ripperdoc-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {ripperdoc-0.2.10.dist-info → ripperdoc-0.3.0.dist-info}/top_level.txt +0 -0
ripperdoc/sdk/client.py
DELETED
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
"""Headless Python SDK for Ripperdoc.
|
|
2
|
-
|
|
3
|
-
`query` helper for simple calls and a `RipperdocClient` for long-lived
|
|
4
|
-
sessions that keep conversation history.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import asyncio
|
|
10
|
-
import os
|
|
11
|
-
import time
|
|
12
|
-
import uuid
|
|
13
|
-
from dataclasses import dataclass, field
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
from typing import (
|
|
16
|
-
Any,
|
|
17
|
-
AsyncIterator,
|
|
18
|
-
Awaitable,
|
|
19
|
-
Callable,
|
|
20
|
-
Dict,
|
|
21
|
-
List,
|
|
22
|
-
Optional,
|
|
23
|
-
Sequence,
|
|
24
|
-
Tuple,
|
|
25
|
-
Union,
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
from ripperdoc.core.default_tools import get_default_tools
|
|
29
|
-
from ripperdoc.core.hooks.llm_callback import build_hook_llm_callback
|
|
30
|
-
from ripperdoc.core.hooks.manager import hook_manager
|
|
31
|
-
from ripperdoc.core.query import QueryContext, query as _core_query
|
|
32
|
-
from ripperdoc.core.permissions import PermissionResult
|
|
33
|
-
from ripperdoc.core.system_prompt import build_system_prompt
|
|
34
|
-
from ripperdoc.core.skills import build_skill_summary, load_all_skills
|
|
35
|
-
from ripperdoc.core.tool import Tool
|
|
36
|
-
from ripperdoc.tools.task_tool import TaskTool
|
|
37
|
-
from ripperdoc.tools.mcp_tools import load_dynamic_mcp_tools_async, merge_tools_with_dynamic
|
|
38
|
-
from ripperdoc.utils.memory import build_memory_instructions
|
|
39
|
-
from ripperdoc.utils.messages import (
|
|
40
|
-
AssistantMessage,
|
|
41
|
-
ProgressMessage,
|
|
42
|
-
UserMessage,
|
|
43
|
-
create_assistant_message,
|
|
44
|
-
create_user_message,
|
|
45
|
-
)
|
|
46
|
-
from ripperdoc.utils.mcp import (
|
|
47
|
-
format_mcp_instructions,
|
|
48
|
-
load_mcp_servers_async,
|
|
49
|
-
shutdown_mcp_runtime,
|
|
50
|
-
)
|
|
51
|
-
from ripperdoc.utils.log import get_logger
|
|
52
|
-
|
|
53
|
-
MessageType = Union[UserMessage, AssistantMessage, ProgressMessage]
|
|
54
|
-
PermissionChecker = Callable[
|
|
55
|
-
[Tool[Any, Any], Any],
|
|
56
|
-
Union[
|
|
57
|
-
PermissionResult,
|
|
58
|
-
Dict[str, Any],
|
|
59
|
-
Tuple[bool, Optional[str]],
|
|
60
|
-
bool,
|
|
61
|
-
Awaitable[Union[PermissionResult, Dict[str, Any], Tuple[bool, Optional[str]], bool]],
|
|
62
|
-
],
|
|
63
|
-
]
|
|
64
|
-
QueryRunner = Callable[
|
|
65
|
-
[
|
|
66
|
-
List[MessageType],
|
|
67
|
-
str,
|
|
68
|
-
Dict[str, str],
|
|
69
|
-
QueryContext,
|
|
70
|
-
Optional[PermissionChecker],
|
|
71
|
-
],
|
|
72
|
-
AsyncIterator[MessageType],
|
|
73
|
-
]
|
|
74
|
-
|
|
75
|
-
_END_OF_STREAM = object()
|
|
76
|
-
|
|
77
|
-
logger = get_logger()
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def _coerce_to_path(path: Union[str, Path]) -> Path:
|
|
81
|
-
return path if isinstance(path, Path) else Path(path)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
@dataclass
|
|
85
|
-
class RipperdocOptions:
|
|
86
|
-
"""Configuration for SDK usage."""
|
|
87
|
-
|
|
88
|
-
tools: Optional[Sequence[Tool[Any, Any]]] = None
|
|
89
|
-
allowed_tools: Optional[Sequence[str]] = None
|
|
90
|
-
disallowed_tools: Optional[Sequence[str]] = None
|
|
91
|
-
yolo_mode: bool = False
|
|
92
|
-
verbose: bool = False
|
|
93
|
-
model: str = "main"
|
|
94
|
-
max_thinking_tokens: int = 0
|
|
95
|
-
context: Dict[str, str] = field(default_factory=dict)
|
|
96
|
-
system_prompt: Optional[str] = None
|
|
97
|
-
additional_instructions: Optional[Union[str, Sequence[str]]] = None
|
|
98
|
-
permission_checker: Optional[PermissionChecker] = None
|
|
99
|
-
cwd: Optional[Union[str, Path]] = None
|
|
100
|
-
|
|
101
|
-
def build_tools(self) -> List[Tool[Any, Any]]:
|
|
102
|
-
"""Create the tool set with allow/deny filters applied."""
|
|
103
|
-
base_tools = list(self.tools) if self.tools is not None else get_default_tools()
|
|
104
|
-
allowed = set(self.allowed_tools) if self.allowed_tools is not None else None
|
|
105
|
-
disallowed = set(self.disallowed_tools or [])
|
|
106
|
-
|
|
107
|
-
filtered: List[Tool[Any, Any]] = []
|
|
108
|
-
for tool in base_tools:
|
|
109
|
-
name = getattr(tool, "name", tool.__class__.__name__)
|
|
110
|
-
if allowed is not None and name not in allowed:
|
|
111
|
-
continue
|
|
112
|
-
if name in disallowed:
|
|
113
|
-
continue
|
|
114
|
-
filtered.append(tool)
|
|
115
|
-
|
|
116
|
-
if allowed is not None and not filtered:
|
|
117
|
-
raise ValueError("No tools remain after applying allowed_tools/disallowed_tools.")
|
|
118
|
-
|
|
119
|
-
# The default Task tool captures the original base tools. If filters are
|
|
120
|
-
# applied, recreate it so the subagent only sees the filtered set.
|
|
121
|
-
if (self.allowed_tools or self.disallowed_tools) and self.tools is None:
|
|
122
|
-
has_task = any(getattr(tool, "name", None) == "Task" for tool in filtered)
|
|
123
|
-
if has_task:
|
|
124
|
-
filtered_base = [tool for tool in filtered if getattr(tool, "name", None) != "Task"]
|
|
125
|
-
|
|
126
|
-
def _filtered_base_provider() -> List[Tool[Any, Any]]:
|
|
127
|
-
return filtered_base
|
|
128
|
-
|
|
129
|
-
filtered = [
|
|
130
|
-
(
|
|
131
|
-
TaskTool(_filtered_base_provider)
|
|
132
|
-
if getattr(tool, "name", None) == "Task"
|
|
133
|
-
else tool
|
|
134
|
-
)
|
|
135
|
-
for tool in filtered
|
|
136
|
-
]
|
|
137
|
-
|
|
138
|
-
return filtered
|
|
139
|
-
|
|
140
|
-
def extra_instructions(self) -> List[str]:
|
|
141
|
-
"""Normalize additional instructions to a list."""
|
|
142
|
-
if self.additional_instructions is None:
|
|
143
|
-
return []
|
|
144
|
-
if isinstance(self.additional_instructions, str):
|
|
145
|
-
return [self.additional_instructions]
|
|
146
|
-
return [text for text in self.additional_instructions if text]
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
class RipperdocClient:
|
|
150
|
-
"""Persistent Ripperdoc session with conversation history."""
|
|
151
|
-
|
|
152
|
-
def __init__(
|
|
153
|
-
self,
|
|
154
|
-
options: Optional[RipperdocOptions] = None,
|
|
155
|
-
query_runner: Optional[QueryRunner] = None,
|
|
156
|
-
) -> None:
|
|
157
|
-
self.options = options or RipperdocOptions()
|
|
158
|
-
self._tools = self.options.build_tools()
|
|
159
|
-
self._query_runner = query_runner or _core_query
|
|
160
|
-
|
|
161
|
-
self._history: List[MessageType] = []
|
|
162
|
-
self._queue: asyncio.Queue = asyncio.Queue()
|
|
163
|
-
self._current_task: Optional[asyncio.Task] = None
|
|
164
|
-
self._current_context: Optional[QueryContext] = None
|
|
165
|
-
self._connected = False
|
|
166
|
-
self._previous_cwd: Optional[Path] = None
|
|
167
|
-
self._session_hook_contexts: List[str] = []
|
|
168
|
-
self._session_id: Optional[str] = None
|
|
169
|
-
self._session_start_time: Optional[float] = None
|
|
170
|
-
self._session_end_sent: bool = False
|
|
171
|
-
|
|
172
|
-
@property
|
|
173
|
-
def tools(self) -> List[Tool[Any, Any]]:
|
|
174
|
-
return self._tools
|
|
175
|
-
|
|
176
|
-
@property
|
|
177
|
-
def history(self) -> List[MessageType]:
|
|
178
|
-
return list(self._history)
|
|
179
|
-
|
|
180
|
-
async def __aenter__(self) -> "RipperdocClient":
|
|
181
|
-
await self.connect()
|
|
182
|
-
return self
|
|
183
|
-
|
|
184
|
-
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: # type: ignore[override]
|
|
185
|
-
await self.disconnect()
|
|
186
|
-
|
|
187
|
-
async def connect(self, prompt: Optional[str] = None) -> None:
|
|
188
|
-
"""Prepare the session and optionally send an initial prompt."""
|
|
189
|
-
if not self._connected:
|
|
190
|
-
if self.options.cwd is not None:
|
|
191
|
-
self._previous_cwd = Path.cwd()
|
|
192
|
-
os.chdir(_coerce_to_path(self.options.cwd))
|
|
193
|
-
self._connected = True
|
|
194
|
-
project_path = _coerce_to_path(self.options.cwd or Path.cwd())
|
|
195
|
-
hook_manager.set_project_dir(project_path)
|
|
196
|
-
self._session_id = self._session_id or str(uuid.uuid4())
|
|
197
|
-
hook_manager.set_session_id(self._session_id)
|
|
198
|
-
hook_manager.set_llm_callback(build_hook_llm_callback())
|
|
199
|
-
try:
|
|
200
|
-
result = await hook_manager.run_session_start_async("startup")
|
|
201
|
-
self._session_hook_contexts = self._collect_hook_contexts(result)
|
|
202
|
-
self._session_start_time = time.time()
|
|
203
|
-
self._session_end_sent = False
|
|
204
|
-
except (OSError, RuntimeError, ConnectionError, ValueError, TypeError) as exc:
|
|
205
|
-
logger.warning(
|
|
206
|
-
"[sdk] SessionStart hook failed: %s: %s",
|
|
207
|
-
type(exc).__name__,
|
|
208
|
-
exc,
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
if prompt:
|
|
212
|
-
await self.query(prompt)
|
|
213
|
-
|
|
214
|
-
async def disconnect(self) -> None:
|
|
215
|
-
"""Tear down the session and restore the working directory."""
|
|
216
|
-
if self._current_context:
|
|
217
|
-
self._current_context.abort_controller.set()
|
|
218
|
-
|
|
219
|
-
if self._current_task and not self._current_task.done():
|
|
220
|
-
self._current_task.cancel()
|
|
221
|
-
try:
|
|
222
|
-
await self._current_task
|
|
223
|
-
except asyncio.CancelledError:
|
|
224
|
-
pass
|
|
225
|
-
|
|
226
|
-
if self._previous_cwd:
|
|
227
|
-
os.chdir(self._previous_cwd)
|
|
228
|
-
self._previous_cwd = None
|
|
229
|
-
|
|
230
|
-
self._connected = False
|
|
231
|
-
if not self._session_end_sent:
|
|
232
|
-
duration = (
|
|
233
|
-
max(time.time() - self._session_start_time, 0.0)
|
|
234
|
-
if self._session_start_time is not None
|
|
235
|
-
else None
|
|
236
|
-
)
|
|
237
|
-
try:
|
|
238
|
-
await hook_manager.run_session_end_async(
|
|
239
|
-
"other",
|
|
240
|
-
duration_seconds=duration,
|
|
241
|
-
message_count=len(self._history),
|
|
242
|
-
)
|
|
243
|
-
except (OSError, RuntimeError, ConnectionError, ValueError, TypeError) as exc:
|
|
244
|
-
logger.warning(
|
|
245
|
-
"[sdk] SessionEnd hook failed: %s: %s",
|
|
246
|
-
type(exc).__name__,
|
|
247
|
-
exc,
|
|
248
|
-
)
|
|
249
|
-
self._session_end_sent = True
|
|
250
|
-
await shutdown_mcp_runtime()
|
|
251
|
-
|
|
252
|
-
async def query(self, prompt: str) -> None:
|
|
253
|
-
"""Send a prompt and start streaming the response."""
|
|
254
|
-
if self._current_task and not self._current_task.done():
|
|
255
|
-
raise RuntimeError(
|
|
256
|
-
"A query is already in progress; wait for it to finish or interrupt it."
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
if not self._connected:
|
|
260
|
-
await self.connect()
|
|
261
|
-
|
|
262
|
-
self._queue = asyncio.Queue()
|
|
263
|
-
|
|
264
|
-
hook_result = await hook_manager.run_user_prompt_submit_async(prompt)
|
|
265
|
-
if hook_result.should_block or not hook_result.should_continue:
|
|
266
|
-
reason = (
|
|
267
|
-
hook_result.block_reason
|
|
268
|
-
or hook_result.stop_reason
|
|
269
|
-
or "Prompt blocked by hook."
|
|
270
|
-
)
|
|
271
|
-
blocked_message = create_assistant_message(str(reason))
|
|
272
|
-
self._history.append(blocked_message)
|
|
273
|
-
await self._queue.put(blocked_message)
|
|
274
|
-
await self._queue.put(_END_OF_STREAM)
|
|
275
|
-
self._current_task = asyncio.create_task(asyncio.sleep(0))
|
|
276
|
-
return
|
|
277
|
-
hook_instructions = self._collect_hook_contexts(hook_result)
|
|
278
|
-
|
|
279
|
-
user_message = create_user_message(prompt)
|
|
280
|
-
history = list(self._history) + [user_message]
|
|
281
|
-
self._history.append(user_message)
|
|
282
|
-
|
|
283
|
-
system_prompt = await self._build_system_prompt(prompt, hook_instructions)
|
|
284
|
-
context = dict(self.options.context)
|
|
285
|
-
|
|
286
|
-
query_context = QueryContext(
|
|
287
|
-
tools=self._tools,
|
|
288
|
-
max_thinking_tokens=self.options.max_thinking_tokens,
|
|
289
|
-
yolo_mode=self.options.yolo_mode,
|
|
290
|
-
model=self.options.model,
|
|
291
|
-
verbose=self.options.verbose,
|
|
292
|
-
)
|
|
293
|
-
self._current_context = query_context
|
|
294
|
-
|
|
295
|
-
async def _runner() -> None:
|
|
296
|
-
try:
|
|
297
|
-
async for message in self._query_runner(
|
|
298
|
-
history,
|
|
299
|
-
system_prompt,
|
|
300
|
-
context,
|
|
301
|
-
query_context,
|
|
302
|
-
self.options.permission_checker,
|
|
303
|
-
):
|
|
304
|
-
if getattr(message, "type", None) in ("user", "assistant"):
|
|
305
|
-
self._history.append(message) # type: ignore[arg-type]
|
|
306
|
-
await self._queue.put(message)
|
|
307
|
-
finally:
|
|
308
|
-
await self._queue.put(_END_OF_STREAM)
|
|
309
|
-
|
|
310
|
-
self._current_task = asyncio.create_task(_runner())
|
|
311
|
-
|
|
312
|
-
async def receive_messages(self) -> AsyncIterator[MessageType]:
|
|
313
|
-
"""Yield messages for the active query."""
|
|
314
|
-
if self._current_task is None:
|
|
315
|
-
raise RuntimeError("No active query to receive messages from.")
|
|
316
|
-
|
|
317
|
-
while True:
|
|
318
|
-
message = await self._queue.get()
|
|
319
|
-
if message is _END_OF_STREAM:
|
|
320
|
-
break
|
|
321
|
-
yield message # type: ignore[misc]
|
|
322
|
-
|
|
323
|
-
async def receive_response(self) -> AsyncIterator[MessageType]:
|
|
324
|
-
"""Alias for receive_messages."""
|
|
325
|
-
async for message in self.receive_messages():
|
|
326
|
-
yield message
|
|
327
|
-
|
|
328
|
-
async def interrupt(self) -> None:
|
|
329
|
-
"""Request cancellation of the active query."""
|
|
330
|
-
if self._current_context:
|
|
331
|
-
self._current_context.abort_controller.set()
|
|
332
|
-
|
|
333
|
-
if self._current_task and not self._current_task.done():
|
|
334
|
-
self._current_task.cancel()
|
|
335
|
-
try:
|
|
336
|
-
await self._current_task
|
|
337
|
-
except asyncio.CancelledError:
|
|
338
|
-
pass
|
|
339
|
-
|
|
340
|
-
await self._queue.put(_END_OF_STREAM)
|
|
341
|
-
|
|
342
|
-
async def _build_system_prompt(
|
|
343
|
-
self, user_prompt: str, hook_instructions: Optional[List[str]] = None
|
|
344
|
-
) -> str:
|
|
345
|
-
if self.options.system_prompt:
|
|
346
|
-
return self.options.system_prompt
|
|
347
|
-
|
|
348
|
-
instructions: List[str] = []
|
|
349
|
-
project_path = _coerce_to_path(self.options.cwd or Path.cwd())
|
|
350
|
-
skill_result = load_all_skills(project_path)
|
|
351
|
-
for err in skill_result.errors:
|
|
352
|
-
logger.warning(
|
|
353
|
-
"[skills] Failed to load skill",
|
|
354
|
-
extra={"path": str(err.path), "reason": err.reason},
|
|
355
|
-
)
|
|
356
|
-
skill_instructions = build_skill_summary(skill_result.skills)
|
|
357
|
-
if skill_instructions:
|
|
358
|
-
instructions.append(skill_instructions)
|
|
359
|
-
instructions.extend(self.options.extra_instructions())
|
|
360
|
-
memory = build_memory_instructions()
|
|
361
|
-
if memory:
|
|
362
|
-
instructions.append(memory)
|
|
363
|
-
if self._session_hook_contexts:
|
|
364
|
-
instructions.extend(self._session_hook_contexts)
|
|
365
|
-
if hook_instructions:
|
|
366
|
-
instructions.extend([text for text in hook_instructions if text])
|
|
367
|
-
|
|
368
|
-
dynamic_tools = await load_dynamic_mcp_tools_async(project_path)
|
|
369
|
-
if dynamic_tools:
|
|
370
|
-
self._tools = merge_tools_with_dynamic(self._tools, dynamic_tools)
|
|
371
|
-
|
|
372
|
-
servers = await load_mcp_servers_async(project_path)
|
|
373
|
-
mcp_instructions = format_mcp_instructions(servers)
|
|
374
|
-
|
|
375
|
-
return build_system_prompt(
|
|
376
|
-
self._tools,
|
|
377
|
-
user_prompt,
|
|
378
|
-
dict(self.options.context),
|
|
379
|
-
instructions or None,
|
|
380
|
-
mcp_instructions=mcp_instructions,
|
|
381
|
-
)
|
|
382
|
-
|
|
383
|
-
def _collect_hook_contexts(self, hook_result: Any) -> List[str]:
|
|
384
|
-
contexts: List[str] = []
|
|
385
|
-
system_message = getattr(hook_result, "system_message", None)
|
|
386
|
-
additional_context = getattr(hook_result, "additional_context", None)
|
|
387
|
-
if system_message:
|
|
388
|
-
contexts.append(str(system_message))
|
|
389
|
-
if additional_context:
|
|
390
|
-
contexts.append(str(additional_context))
|
|
391
|
-
return contexts
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
async def query(
|
|
395
|
-
prompt: str,
|
|
396
|
-
options: Optional[RipperdocOptions] = None,
|
|
397
|
-
query_runner: Optional[QueryRunner] = None,
|
|
398
|
-
) -> AsyncIterator[MessageType]:
|
|
399
|
-
"""One-shot helper: run a prompt in a fresh session."""
|
|
400
|
-
client = RipperdocClient(options=options, query_runner=query_runner)
|
|
401
|
-
await client.connect()
|
|
402
|
-
await client.query(prompt)
|
|
403
|
-
|
|
404
|
-
try:
|
|
405
|
-
async for message in client.receive_messages():
|
|
406
|
-
yield message
|
|
407
|
-
finally:
|
|
408
|
-
await client.disconnect()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|