Add events for long running things
This commit is contained in:
parent
9353725dd2
commit
1b39fad2d4
1 changed files with 87 additions and 25 deletions
112
filesystem.py
112
filesystem.py
|
|
@ -87,6 +87,26 @@ class Tools:
|
||||||
else:
|
else:
|
||||||
logger.setLevel(logging.INFO)
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
async def _emit_status(self, __event_emitter__, description: str, done: bool = False, **extra: Any) -> None:
|
||||||
|
"""Safely emit a status event if an emitter is provided.
|
||||||
|
|
||||||
|
The emitter expects a payload of the form:
|
||||||
|
{"type": "status", "data": {"description": str, "done": bool, ...}}
|
||||||
|
"""
|
||||||
|
if __event_emitter__ is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
payload: Dict[str, Any] = {
|
||||||
|
"type": "status",
|
||||||
|
"data": {"description": description, "done": done},
|
||||||
|
}
|
||||||
|
if extra:
|
||||||
|
payload["data"].update(extra)
|
||||||
|
await __event_emitter__(payload)
|
||||||
|
except Exception:
|
||||||
|
# Never allow status emission failures to interrupt tool execution
|
||||||
|
pass
|
||||||
|
|
||||||
def _result(self, ok: bool, action: str = "", subject_type: str = "", **kwargs) -> Dict[str, Any]:
|
def _result(self, ok: bool, action: str = "", subject_type: str = "", **kwargs) -> Dict[str, Any]:
|
||||||
"""Helper function to create consistent result format."""
|
"""Helper function to create consistent result format."""
|
||||||
result = {"ok": ok, "action": action, "subject_type": subject_type}
|
result = {"ok": ok, "action": action, "subject_type": subject_type}
|
||||||
|
|
@ -1492,7 +1512,8 @@ class Tools:
|
||||||
base_dir: Optional[str] = None,
|
base_dir: Optional[str] = None,
|
||||||
case_sensitive: bool = True,
|
case_sensitive: bool = True,
|
||||||
include_content: bool = True,
|
include_content: bool = True,
|
||||||
max_results: int = 100
|
max_results: int = 100,
|
||||||
|
__event_emitter__=None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Search for files containing the keyword in their names or content.
|
Search for files containing the keyword in their names or content.
|
||||||
|
|
@ -1506,6 +1527,7 @@ class Tools:
|
||||||
try:
|
try:
|
||||||
base_path = base_dir if base_dir else "."
|
base_path = base_dir if base_dir else "."
|
||||||
search_path = self._resolve_under_restriction(base_path)
|
search_path = self._resolve_under_restriction(base_path)
|
||||||
|
await self._emit_status(__event_emitter__, f"Searching for '{keyword}' in {self._get_relative_path(search_path)}")
|
||||||
|
|
||||||
if not await aiofiles.os.path.exists(search_path):
|
if not await aiofiles.os.path.exists(search_path):
|
||||||
return self._result(
|
return self._result(
|
||||||
|
|
@ -1533,7 +1555,7 @@ class Tools:
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Search for keyword '{keyword}' completed with {len(matches)} matches")
|
logger.info(f"Search for keyword '{keyword}' completed with {len(matches)} matches")
|
||||||
return self._result(
|
result = self._result(
|
||||||
True,
|
True,
|
||||||
action="search",
|
action="search",
|
||||||
subject_type="directory",
|
subject_type="directory",
|
||||||
|
|
@ -1543,6 +1565,8 @@ class Tools:
|
||||||
case_sensitive=case_sensitive,
|
case_sensitive=case_sensitive,
|
||||||
truncated=len(matches) >= max_results
|
truncated=len(matches) >= max_results
|
||||||
)
|
)
|
||||||
|
await self._emit_status(__event_emitter__, f"Search complete: {len(matches)} match(es)", done=True)
|
||||||
|
return result
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return self._result(False, action="search", subject_type="directory", error=str(e))
|
return self._result(False, action="search", subject_type="directory", error=str(e))
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
|
|
@ -1613,7 +1637,8 @@ class Tools:
|
||||||
base_dir: Optional[str] = None,
|
base_dir: Optional[str] = None,
|
||||||
case_sensitive: bool = True,
|
case_sensitive: bool = True,
|
||||||
include_extensions: bool = True,
|
include_extensions: bool = True,
|
||||||
max_results: int = 100
|
max_results: int = 100,
|
||||||
|
__event_emitter__=None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Search for files by name and/or extension only (no content search).
|
Search for files by name and/or extension only (no content search).
|
||||||
|
|
@ -1627,6 +1652,7 @@ class Tools:
|
||||||
try:
|
try:
|
||||||
base_path = base_dir if base_dir else "."
|
base_path = base_dir if base_dir else "."
|
||||||
search_path = self._resolve_under_restriction(base_path)
|
search_path = self._resolve_under_restriction(base_path)
|
||||||
|
await self._emit_status(__event_emitter__, f"Searching names for '{pattern}' in {self._get_relative_path(search_path)}")
|
||||||
|
|
||||||
if not await aiofiles.os.path.exists(search_path):
|
if not await aiofiles.os.path.exists(search_path):
|
||||||
return self._result(
|
return self._result(
|
||||||
|
|
@ -1654,7 +1680,7 @@ class Tools:
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"File name search for pattern '{pattern}' completed with {len(matches)} matches")
|
logger.info(f"File name search for pattern '{pattern}' completed with {len(matches)} matches")
|
||||||
return self._result(
|
result = self._result(
|
||||||
True,
|
True,
|
||||||
action="search_file_names",
|
action="search_file_names",
|
||||||
subject_type="directory",
|
subject_type="directory",
|
||||||
|
|
@ -1665,6 +1691,8 @@ class Tools:
|
||||||
include_extensions=include_extensions,
|
include_extensions=include_extensions,
|
||||||
truncated=len(matches) >= max_results
|
truncated=len(matches) >= max_results
|
||||||
)
|
)
|
||||||
|
await self._emit_status(__event_emitter__, f"Name search complete: {len(matches)} match(es)", done=True)
|
||||||
|
return result
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return self._result(False, action="search_file_names", subject_type="directory", error=str(e))
|
return self._result(False, action="search_file_names", subject_type="directory", error=str(e))
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
|
|
@ -1985,7 +2013,8 @@ class Tools:
|
||||||
prompt: str = "Please analyze this file",
|
prompt: str = "Please analyze this file",
|
||||||
base_dir: Optional[str] = None,
|
base_dir: Optional[str] = None,
|
||||||
model: Optional[str] = None,
|
model: Optional[str] = None,
|
||||||
max_tokens: int = 1000
|
max_tokens: int = 1000,
|
||||||
|
__event_emitter__=None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Upload a file to OpenRouter for analysis by a vision-capable model.
|
Upload a file to OpenRouter for analysis by a vision-capable model.
|
||||||
|
|
@ -2008,6 +2037,7 @@ class Tools:
|
||||||
|
|
||||||
base_path = base_dir if base_dir else "."
|
base_path = base_dir if base_dir else "."
|
||||||
file_path = self._resolve_under_restriction(os.path.join(base_path, file_name))
|
file_path = self._resolve_under_restriction(os.path.join(base_path, file_name))
|
||||||
|
await self._emit_status(__event_emitter__, f"Uploading to OpenRouter: {self._get_relative_path(file_path)}")
|
||||||
|
|
||||||
if not await aiofiles.os.path.exists(file_path):
|
if not await aiofiles.os.path.exists(file_path):
|
||||||
return self._result(
|
return self._result(
|
||||||
|
|
@ -2169,7 +2199,7 @@ class Tools:
|
||||||
analysis = result["choices"][0]["message"]["content"]
|
analysis = result["choices"][0]["message"]["content"]
|
||||||
|
|
||||||
logger.info(f"File '{file_name}' successfully analyzed by OpenRouter model '{model_name}'")
|
logger.info(f"File '{file_name}' successfully analyzed by OpenRouter model '{model_name}'")
|
||||||
return self._result(
|
final = self._result(
|
||||||
True,
|
True,
|
||||||
action="openrouter_upload",
|
action="openrouter_upload",
|
||||||
subject_type="file",
|
subject_type="file",
|
||||||
|
|
@ -2182,6 +2212,8 @@ class Tools:
|
||||||
usage=result.get("usage", {}),
|
usage=result.get("usage", {}),
|
||||||
response_id=result.get("id", "")
|
response_id=result.get("id", "")
|
||||||
)
|
)
|
||||||
|
await self._emit_status(__event_emitter__, "OpenRouter analysis complete", done=True)
|
||||||
|
return final
|
||||||
else:
|
else:
|
||||||
return self._result(
|
return self._result(
|
||||||
False,
|
False,
|
||||||
|
|
@ -2199,13 +2231,15 @@ class Tools:
|
||||||
error_text = await response.text()
|
error_text = await response.text()
|
||||||
error_msg += f" - {error_text}"
|
error_msg += f" - {error_text}"
|
||||||
|
|
||||||
return self._result(
|
failed = self._result(
|
||||||
False,
|
False,
|
||||||
action="openrouter_upload",
|
action="openrouter_upload",
|
||||||
subject_type="file",
|
subject_type="file",
|
||||||
error=error_msg,
|
error=error_msg,
|
||||||
status_code=response.status
|
status_code=response.status
|
||||||
)
|
)
|
||||||
|
await self._emit_status(__event_emitter__, f"OpenRouter request failed: {response.status}", done=True)
|
||||||
|
return failed
|
||||||
|
|
||||||
except aiohttp.ClientError as e:
|
except aiohttp.ClientError as e:
|
||||||
return self._result(
|
return self._result(
|
||||||
|
|
@ -2235,7 +2269,8 @@ class Tools:
|
||||||
prompt: str = "Please analyze these files",
|
prompt: str = "Please analyze these files",
|
||||||
base_dir: Optional[str] = None,
|
base_dir: Optional[str] = None,
|
||||||
model: Optional[str] = None,
|
model: Optional[str] = None,
|
||||||
max_tokens: int = 1000
|
max_tokens: int = 1000,
|
||||||
|
__event_emitter__=None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Upload multiple files to OpenRouter for batch analysis.
|
Upload multiple files to OpenRouter for batch analysis.
|
||||||
|
|
@ -2259,13 +2294,16 @@ class Tools:
|
||||||
successful = 0
|
successful = 0
|
||||||
failed = 0
|
failed = 0
|
||||||
|
|
||||||
for file_name in file_names:
|
total = len(file_names)
|
||||||
|
for index, file_name in enumerate(file_names, start=1):
|
||||||
|
await self._emit_status(__event_emitter__, f"[{index}/{total}] Uploading: {file_name}")
|
||||||
result = await self._upload_file_to_openrouter(
|
result = await self._upload_file_to_openrouter(
|
||||||
file_name=file_name,
|
file_name=file_name,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
base_dir=base_dir,
|
base_dir=base_dir,
|
||||||
model=model,
|
model=model,
|
||||||
max_tokens=max_tokens
|
max_tokens=max_tokens,
|
||||||
|
__event_emitter__=__event_emitter__
|
||||||
)
|
)
|
||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
|
|
@ -2280,7 +2318,7 @@ class Tools:
|
||||||
failed += 1
|
failed += 1
|
||||||
|
|
||||||
logger.info(f"Batch upload completed: {successful} successful, {failed} failed")
|
logger.info(f"Batch upload completed: {successful} successful, {failed} failed")
|
||||||
return self._result(
|
final = self._result(
|
||||||
True,
|
True,
|
||||||
action="openrouter_batch_upload",
|
action="openrouter_batch_upload",
|
||||||
subject_type="files",
|
subject_type="files",
|
||||||
|
|
@ -2289,6 +2327,8 @@ class Tools:
|
||||||
failed=failed,
|
failed=failed,
|
||||||
results=results
|
results=results
|
||||||
)
|
)
|
||||||
|
await self._emit_status(__event_emitter__, f"Batch upload complete: {successful} successful, {failed} failed", done=True)
|
||||||
|
return final
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return self._result(
|
return self._result(
|
||||||
|
|
@ -2304,7 +2344,8 @@ class Tools:
|
||||||
base_dir: Optional[str] = None,
|
base_dir: Optional[str] = None,
|
||||||
transcription_mode: str = "auto",
|
transcription_mode: str = "auto",
|
||||||
language: Optional[str] = None,
|
language: Optional[str] = None,
|
||||||
output_format: str = "text"
|
output_format: str = "text",
|
||||||
|
__event_emitter__=None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Transcribe text content from any supported file type using OpenRouter models.
|
Transcribe text content from any supported file type using OpenRouter models.
|
||||||
|
|
@ -2401,11 +2442,13 @@ class Tools:
|
||||||
prompt += f" The text is expected to be in {language}."
|
prompt += f" The text is expected to be in {language}."
|
||||||
|
|
||||||
# Use the existing OpenRouter upload method
|
# Use the existing OpenRouter upload method
|
||||||
|
await self._emit_status(__event_emitter__, f"Transcribing via OpenRouter: {file_name}")
|
||||||
result = await self._upload_file_to_openrouter(
|
result = await self._upload_file_to_openrouter(
|
||||||
file_name=file_name,
|
file_name=file_name,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
base_dir=base_dir,
|
base_dir=base_dir,
|
||||||
max_tokens=10000 # Higher token limit for transcription
|
max_tokens=10000, # Higher token limit for transcription
|
||||||
|
__event_emitter__=__event_emitter__
|
||||||
)
|
)
|
||||||
|
|
||||||
if result["ok"]:
|
if result["ok"]:
|
||||||
|
|
@ -2419,7 +2462,7 @@ class Tools:
|
||||||
transcribed_text = re.sub(r'\n\s*\n', '\n\n', transcribed_text)
|
transcribed_text = re.sub(r'\n\s*\n', '\n\n', transcribed_text)
|
||||||
|
|
||||||
logger.info(f"File '{file_name}' transcribed successfully using mode '{transcription_mode}'")
|
logger.info(f"File '{file_name}' transcribed successfully using mode '{transcription_mode}'")
|
||||||
return self._result(
|
final = self._result(
|
||||||
True,
|
True,
|
||||||
action="transcribe",
|
action="transcribe",
|
||||||
subject_type="file",
|
subject_type="file",
|
||||||
|
|
@ -2435,6 +2478,8 @@ class Tools:
|
||||||
character_count=len(transcribed_text) if transcribed_text else 0,
|
character_count=len(transcribed_text) if transcribed_text else 0,
|
||||||
usage=result.get("usage", {})
|
usage=result.get("usage", {})
|
||||||
)
|
)
|
||||||
|
await self._emit_status(__event_emitter__, "Transcription complete", done=True)
|
||||||
|
return final
|
||||||
else:
|
else:
|
||||||
return self._result(
|
return self._result(
|
||||||
False,
|
False,
|
||||||
|
|
@ -2465,7 +2510,8 @@ class Tools:
|
||||||
base_dir: Optional[str] = None,
|
base_dir: Optional[str] = None,
|
||||||
transcription_mode: str = "auto",
|
transcription_mode: str = "auto",
|
||||||
language: Optional[str] = None,
|
language: Optional[str] = None,
|
||||||
output_format: str = "text"
|
output_format: str = "text",
|
||||||
|
__event_emitter__=None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Transcribe multiple files in batch using OpenRouter models.
|
Transcribe multiple files in batch using OpenRouter models.
|
||||||
|
|
@ -2492,13 +2538,16 @@ class Tools:
|
||||||
total_words = 0
|
total_words = 0
|
||||||
total_characters = 0
|
total_characters = 0
|
||||||
|
|
||||||
for file_name in file_names:
|
total = len(file_names)
|
||||||
|
for index, file_name in enumerate(file_names, start=1):
|
||||||
|
await self._emit_status(__event_emitter__, f"[{index}/{total}] Transcribing: {file_name}")
|
||||||
result = await self.transcribe_file(
|
result = await self.transcribe_file(
|
||||||
file_name=file_name,
|
file_name=file_name,
|
||||||
base_dir=base_dir,
|
base_dir=base_dir,
|
||||||
transcription_mode=transcription_mode,
|
transcription_mode=transcription_mode,
|
||||||
language=language,
|
language=language,
|
||||||
output_format=output_format
|
output_format=output_format,
|
||||||
|
__event_emitter__=__event_emitter__
|
||||||
)
|
)
|
||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
|
|
@ -2515,7 +2564,7 @@ class Tools:
|
||||||
failed += 1
|
failed += 1
|
||||||
|
|
||||||
logger.info(f"Batch transcription completed: {successful} successful, {failed} failed")
|
logger.info(f"Batch transcription completed: {successful} successful, {failed} failed")
|
||||||
return self._result(
|
final = self._result(
|
||||||
True,
|
True,
|
||||||
action="batch_transcribe",
|
action="batch_transcribe",
|
||||||
subject_type="files",
|
subject_type="files",
|
||||||
|
|
@ -2529,6 +2578,8 @@ class Tools:
|
||||||
language=language,
|
language=language,
|
||||||
results=results
|
results=results
|
||||||
)
|
)
|
||||||
|
await self._emit_status(__event_emitter__, f"Batch transcription complete: {successful} successful, {failed} failed", done=True)
|
||||||
|
return final
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return self._result(
|
return self._result(
|
||||||
|
|
@ -2544,7 +2595,8 @@ class Tools:
|
||||||
base_dir: Optional[str] = None,
|
base_dir: Optional[str] = None,
|
||||||
description_type: str = "detailed",
|
description_type: str = "detailed",
|
||||||
model: Optional[str] = None,
|
model: Optional[str] = None,
|
||||||
max_tokens: int = 1000
|
max_tokens: int = 1000,
|
||||||
|
__event_emitter__=None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Describe an image using OpenRouter vision models.
|
Describe an image using OpenRouter vision models.
|
||||||
|
|
@ -2610,19 +2662,21 @@ class Tools:
|
||||||
prompt = f"Please describe this image with a focus on: {description_type}"
|
prompt = f"Please describe this image with a focus on: {description_type}"
|
||||||
|
|
||||||
# Use the existing OpenRouter upload method
|
# Use the existing OpenRouter upload method
|
||||||
|
await self._emit_status(__event_emitter__, f"Describing image via OpenRouter: {file_name}")
|
||||||
result = await self._upload_file_to_openrouter(
|
result = await self._upload_file_to_openrouter(
|
||||||
file_name=file_name,
|
file_name=file_name,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
base_dir=base_dir,
|
base_dir=base_dir,
|
||||||
model=model,
|
model=model,
|
||||||
max_tokens=max_tokens
|
max_tokens=max_tokens,
|
||||||
|
__event_emitter__=__event_emitter__
|
||||||
)
|
)
|
||||||
|
|
||||||
if result["ok"]:
|
if result["ok"]:
|
||||||
description = result.get("analysis", "")
|
description = result.get("analysis", "")
|
||||||
|
|
||||||
logger.info(f"Image '{file_name}' described successfully using '{description_type}' style")
|
logger.info(f"Image '{file_name}' described successfully using '{description_type}' style")
|
||||||
return self._result(
|
final = self._result(
|
||||||
True,
|
True,
|
||||||
action="describe_image",
|
action="describe_image",
|
||||||
subject_type="file",
|
subject_type="file",
|
||||||
|
|
@ -2637,6 +2691,8 @@ class Tools:
|
||||||
usage=result.get("usage", {}),
|
usage=result.get("usage", {}),
|
||||||
response_id=result.get("response_id", "")
|
response_id=result.get("response_id", "")
|
||||||
)
|
)
|
||||||
|
await self._emit_status(__event_emitter__, "Image description complete", done=True)
|
||||||
|
return final
|
||||||
else:
|
else:
|
||||||
return self._result(
|
return self._result(
|
||||||
False,
|
False,
|
||||||
|
|
@ -2667,7 +2723,8 @@ class Tools:
|
||||||
base_dir: Optional[str] = None,
|
base_dir: Optional[str] = None,
|
||||||
description_type: str = "detailed",
|
description_type: str = "detailed",
|
||||||
model: Optional[str] = None,
|
model: Optional[str] = None,
|
||||||
max_tokens: int = 1000
|
max_tokens: int = 1000,
|
||||||
|
__event_emitter__=None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Describe multiple images in batch using OpenRouter vision models.
|
Describe multiple images in batch using OpenRouter vision models.
|
||||||
|
|
@ -2694,13 +2751,16 @@ class Tools:
|
||||||
total_words = 0
|
total_words = 0
|
||||||
total_characters = 0
|
total_characters = 0
|
||||||
|
|
||||||
for file_name in file_names:
|
total = len(file_names)
|
||||||
|
for index, file_name in enumerate(file_names, start=1):
|
||||||
|
await self._emit_status(__event_emitter__, f"[{index}/{total}] Describing image: {file_name}")
|
||||||
result = await self.describe_image(
|
result = await self.describe_image(
|
||||||
file_name=file_name,
|
file_name=file_name,
|
||||||
base_dir=base_dir,
|
base_dir=base_dir,
|
||||||
description_type=description_type,
|
description_type=description_type,
|
||||||
model=model,
|
model=model,
|
||||||
max_tokens=max_tokens
|
max_tokens=max_tokens,
|
||||||
|
__event_emitter__=__event_emitter__
|
||||||
)
|
)
|
||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
|
|
@ -2717,7 +2777,7 @@ class Tools:
|
||||||
failed += 1
|
failed += 1
|
||||||
|
|
||||||
logger.info(f"Batch image description completed: {successful} successful, {failed} failed")
|
logger.info(f"Batch image description completed: {successful} successful, {failed} failed")
|
||||||
return self._result(
|
final = self._result(
|
||||||
True,
|
True,
|
||||||
action="batch_describe_images",
|
action="batch_describe_images",
|
||||||
subject_type="files",
|
subject_type="files",
|
||||||
|
|
@ -2730,6 +2790,8 @@ class Tools:
|
||||||
model=model or self.valves.openrouter_model,
|
model=model or self.valves.openrouter_model,
|
||||||
results=results
|
results=results
|
||||||
)
|
)
|
||||||
|
await self._emit_status(__event_emitter__, f"Batch image description complete: {successful} successful, {failed} failed", done=True)
|
||||||
|
return final
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return self._result(
|
return self._result(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue