agent_os_kernel 3.1.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.
- agent_control_plane/__init__.py +662 -0
- agent_control_plane/a2a_adapter.py +543 -0
- agent_control_plane/adapter.py +417 -0
- agent_control_plane/agent_hibernation.py +394 -0
- agent_control_plane/agent_kernel.py +470 -0
- agent_control_plane/compliance.py +720 -0
- agent_control_plane/constraint_graphs.py +478 -0
- agent_control_plane/control_plane.py +854 -0
- agent_control_plane/example_executors.py +195 -0
- agent_control_plane/execution_engine.py +231 -0
- agent_control_plane/flight_recorder.py +846 -0
- agent_control_plane/governance_layer.py +435 -0
- agent_control_plane/hf_utils.py +563 -0
- agent_control_plane/interfaces/__init__.py +55 -0
- agent_control_plane/interfaces/kernel_interface.py +361 -0
- agent_control_plane/interfaces/plugin_interface.py +497 -0
- agent_control_plane/interfaces/protocol_interfaces.py +387 -0
- agent_control_plane/kernel_space.py +1009 -0
- agent_control_plane/langchain_adapter.py +424 -0
- agent_control_plane/lifecycle.py +3113 -0
- agent_control_plane/mcp_adapter.py +653 -0
- agent_control_plane/ml_safety.py +563 -0
- agent_control_plane/multimodal.py +727 -0
- agent_control_plane/mute_agent.py +422 -0
- agent_control_plane/observability.py +787 -0
- agent_control_plane/orchestrator.py +482 -0
- agent_control_plane/plugin_registry.py +750 -0
- agent_control_plane/policy_engine.py +954 -0
- agent_control_plane/process_isolation.py +777 -0
- agent_control_plane/shadow_mode.py +310 -0
- agent_control_plane/signals.py +493 -0
- agent_control_plane/supervisor_agents.py +430 -0
- agent_control_plane/time_travel_debugger.py +557 -0
- agent_control_plane/tool_registry.py +452 -0
- agent_control_plane/vfs.py +697 -0
- agent_kernel/__init__.py +69 -0
- agent_kernel/analyzer.py +435 -0
- agent_kernel/auditor.py +36 -0
- agent_kernel/completeness_auditor.py +237 -0
- agent_kernel/detector.py +203 -0
- agent_kernel/kernel.py +744 -0
- agent_kernel/memory_manager.py +85 -0
- agent_kernel/models.py +374 -0
- agent_kernel/nudge_mechanism.py +263 -0
- agent_kernel/outcome_analyzer.py +338 -0
- agent_kernel/patcher.py +582 -0
- agent_kernel/semantic_analyzer.py +316 -0
- agent_kernel/semantic_purge.py +349 -0
- agent_kernel/simulator.py +449 -0
- agent_kernel/teacher.py +85 -0
- agent_kernel/triage.py +152 -0
- agent_os/__init__.py +409 -0
- agent_os/_adversarial_impl.py +200 -0
- agent_os/_circuit_breaker_impl.py +232 -0
- agent_os/_mcp_metrics.py +193 -0
- agent_os/adversarial.py +20 -0
- agent_os/agents_compat.py +490 -0
- agent_os/audit_logger.py +135 -0
- agent_os/base_agent.py +651 -0
- agent_os/circuit_breaker.py +34 -0
- agent_os/cli/__init__.py +659 -0
- agent_os/cli/cmd_audit.py +128 -0
- agent_os/cli/cmd_init.py +152 -0
- agent_os/cli/cmd_policy.py +41 -0
- agent_os/cli/cmd_policy_gen.py +180 -0
- agent_os/cli/cmd_validate.py +258 -0
- agent_os/cli/mcp_scan.py +265 -0
- agent_os/cli/output.py +192 -0
- agent_os/cli/policy_checker.py +330 -0
- agent_os/compat.py +74 -0
- agent_os/constraint_graph.py +234 -0
- agent_os/content_governance.py +140 -0
- agent_os/context_budget.py +305 -0
- agent_os/credential_redactor.py +224 -0
- agent_os/diff_policy.py +89 -0
- agent_os/egress_policy.py +159 -0
- agent_os/escalation.py +276 -0
- agent_os/event_bus.py +124 -0
- agent_os/exceptions.py +180 -0
- agent_os/execution_context_policy.py +141 -0
- agent_os/github_enterprise.py +96 -0
- agent_os/health.py +20 -0
- agent_os/integrations/__init__.py +279 -0
- agent_os/integrations/a2a_adapter.py +279 -0
- agent_os/integrations/agent_lightning/__init__.py +30 -0
- agent_os/integrations/anthropic_adapter.py +420 -0
- agent_os/integrations/autogen_adapter.py +620 -0
- agent_os/integrations/base.py +1137 -0
- agent_os/integrations/compat.py +229 -0
- agent_os/integrations/config.py +98 -0
- agent_os/integrations/conversation_guardian.py +957 -0
- agent_os/integrations/crewai_adapter.py +467 -0
- agent_os/integrations/drift_detector.py +425 -0
- agent_os/integrations/dry_run.py +124 -0
- agent_os/integrations/escalation.py +582 -0
- agent_os/integrations/gemini_adapter.py +364 -0
- agent_os/integrations/google_adk_adapter.py +633 -0
- agent_os/integrations/guardrails_adapter.py +394 -0
- agent_os/integrations/health.py +197 -0
- agent_os/integrations/langchain_adapter.py +654 -0
- agent_os/integrations/llamafirewall.py +343 -0
- agent_os/integrations/llamaindex_adapter.py +188 -0
- agent_os/integrations/logging.py +191 -0
- agent_os/integrations/maf_adapter.py +631 -0
- agent_os/integrations/mistral_adapter.py +365 -0
- agent_os/integrations/openai_adapter.py +816 -0
- agent_os/integrations/openai_agents_sdk.py +406 -0
- agent_os/integrations/policy_compose.py +171 -0
- agent_os/integrations/profiling.py +144 -0
- agent_os/integrations/pydantic_ai_adapter.py +420 -0
- agent_os/integrations/rate_limiter.py +130 -0
- agent_os/integrations/rbac.py +143 -0
- agent_os/integrations/registry.py +113 -0
- agent_os/integrations/scope_guard.py +303 -0
- agent_os/integrations/semantic_kernel_adapter.py +769 -0
- agent_os/integrations/smolagents_adapter.py +629 -0
- agent_os/integrations/templates.py +178 -0
- agent_os/integrations/token_budget.py +134 -0
- agent_os/integrations/tool_aliases.py +190 -0
- agent_os/integrations/webhooks.py +177 -0
- agent_os/lite.py +208 -0
- agent_os/mcp_gateway.py +385 -0
- agent_os/mcp_message_signer.py +273 -0
- agent_os/mcp_protocols.py +161 -0
- agent_os/mcp_response_scanner.py +232 -0
- agent_os/mcp_security.py +924 -0
- agent_os/mcp_session_auth.py +231 -0
- agent_os/mcp_sliding_rate_limiter.py +184 -0
- agent_os/memory_guard.py +409 -0
- agent_os/metrics.py +134 -0
- agent_os/mute.py +428 -0
- agent_os/mute_agent.py +209 -0
- agent_os/policies/__init__.py +77 -0
- agent_os/policies/async_evaluator.py +275 -0
- agent_os/policies/backends.py +670 -0
- agent_os/policies/bridge.py +169 -0
- agent_os/policies/budget.py +85 -0
- agent_os/policies/cli.py +294 -0
- agent_os/policies/conflict_resolution.py +270 -0
- agent_os/policies/data_classification.py +252 -0
- agent_os/policies/evaluator.py +239 -0
- agent_os/policies/policy_schema.json +228 -0
- agent_os/policies/rate_limiting.py +145 -0
- agent_os/policies/schema.py +115 -0
- agent_os/policies/shared.py +331 -0
- agent_os/prompt_injection.py +694 -0
- agent_os/providers.py +182 -0
- agent_os/py.typed +0 -0
- agent_os/retry.py +81 -0
- agent_os/reversibility.py +251 -0
- agent_os/sandbox.py +432 -0
- agent_os/sandbox_provider.py +140 -0
- agent_os/secure_codegen.py +525 -0
- agent_os/security_skills.py +538 -0
- agent_os/semantic_policy.py +422 -0
- agent_os/server/__init__.py +15 -0
- agent_os/server/__main__.py +25 -0
- agent_os/server/app.py +277 -0
- agent_os/server/models.py +104 -0
- agent_os/shift_left_metrics.py +130 -0
- agent_os/stateless.py +742 -0
- agent_os/supervisor.py +148 -0
- agent_os/task_outcome.py +148 -0
- agent_os/transparency.py +181 -0
- agent_os/trust_root.py +128 -0
- agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
- agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
- agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
- agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
- agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
- agent_os_observability/__init__.py +27 -0
- agent_os_observability/dashboards.py +898 -0
- agent_os_observability/metrics.py +398 -0
- agent_os_observability/server.py +223 -0
- agent_os_observability/tracer.py +232 -0
- agent_primitives/__init__.py +24 -0
- agent_primitives/failures.py +84 -0
- agent_primitives/py.typed +0 -0
- amb_core/__init__.py +177 -0
- amb_core/adapters/__init__.py +57 -0
- amb_core/adapters/aws_sqs_broker.py +376 -0
- amb_core/adapters/azure_servicebus_broker.py +340 -0
- amb_core/adapters/kafka_broker.py +260 -0
- amb_core/adapters/nats_broker.py +285 -0
- amb_core/adapters/rabbitmq_broker.py +235 -0
- amb_core/adapters/redis_broker.py +262 -0
- amb_core/broker.py +145 -0
- amb_core/bus.py +481 -0
- amb_core/cloudevents.py +509 -0
- amb_core/dlq.py +345 -0
- amb_core/hf_utils.py +536 -0
- amb_core/memory_broker.py +410 -0
- amb_core/models.py +141 -0
- amb_core/persistence.py +529 -0
- amb_core/schema.py +294 -0
- amb_core/tracing.py +358 -0
- atr/__init__.py +640 -0
- atr/access.py +348 -0
- atr/composition.py +645 -0
- atr/decorator.py +357 -0
- atr/executor.py +384 -0
- atr/health.py +557 -0
- atr/hf_utils.py +449 -0
- atr/injection.py +422 -0
- atr/metrics.py +440 -0
- atr/policies.py +403 -0
- atr/py.typed +2 -0
- atr/registry.py +452 -0
- atr/schema.py +480 -0
- atr/tools/safe/__init__.py +75 -0
- atr/tools/safe/calculator.py +467 -0
- atr/tools/safe/datetime_tool.py +443 -0
- atr/tools/safe/file_reader.py +402 -0
- atr/tools/safe/http_client.py +316 -0
- atr/tools/safe/json_parser.py +374 -0
- atr/tools/safe/text_tool.py +537 -0
- atr/tools/safe/toolkit.py +175 -0
- caas/__init__.py +162 -0
- caas/api/__init__.py +7 -0
- caas/api/server.py +1328 -0
- caas/caching.py +834 -0
- caas/cli.py +210 -0
- caas/conversation.py +223 -0
- caas/decay.py +72 -0
- caas/detection/__init__.py +9 -0
- caas/detection/detector.py +238 -0
- caas/enrichment.py +130 -0
- caas/gateway/__init__.py +27 -0
- caas/gateway/trust_gateway.py +474 -0
- caas/hf_utils.py +479 -0
- caas/ingestion/__init__.py +23 -0
- caas/ingestion/processors.py +253 -0
- caas/ingestion/structure_parser.py +188 -0
- caas/models.py +356 -0
- caas/pragmatic_truth.py +444 -0
- caas/routing/__init__.py +10 -0
- caas/routing/heuristic_router.py +58 -0
- caas/storage/__init__.py +9 -0
- caas/storage/store.py +389 -0
- caas/triad.py +213 -0
- caas/tuning/__init__.py +9 -0
- caas/tuning/tuner.py +329 -0
- caas/vfs/__init__.py +14 -0
- caas/vfs/filesystem.py +452 -0
- cmvk/__init__.py +218 -0
- cmvk/audit.py +402 -0
- cmvk/benchmarks.py +478 -0
- cmvk/constitutional.py +904 -0
- cmvk/hf_utils.py +301 -0
- cmvk/metrics.py +473 -0
- cmvk/profiles.py +300 -0
- cmvk/py.typed +0 -0
- cmvk/types.py +12 -0
- cmvk/verification.py +956 -0
- emk/__init__.py +89 -0
- emk/causal.py +352 -0
- emk/hf_utils.py +421 -0
- emk/indexer.py +83 -0
- emk/py.typed +0 -0
- emk/schema.py +204 -0
- emk/sleep_cycle.py +347 -0
- emk/store.py +281 -0
- iatp/__init__.py +166 -0
- iatp/attestation.py +461 -0
- iatp/cli.py +317 -0
- iatp/hf_utils.py +472 -0
- iatp/ipc_pipes.py +580 -0
- iatp/main.py +412 -0
- iatp/models/__init__.py +447 -0
- iatp/policy_engine.py +337 -0
- iatp/py.typed +2 -0
- iatp/recovery.py +321 -0
- iatp/security/__init__.py +270 -0
- iatp/sidecar/__init__.py +519 -0
- iatp/telemetry/__init__.py +164 -0
- iatp/tests/__init__.py +1 -0
- iatp/tests/test_attestation.py +370 -0
- iatp/tests/test_cli.py +131 -0
- iatp/tests/test_ed25519_attestation.py +211 -0
- iatp/tests/test_models.py +130 -0
- iatp/tests/test_policy_engine.py +347 -0
- iatp/tests/test_recovery.py +281 -0
- iatp/tests/test_security.py +222 -0
- iatp/tests/test_sidecar.py +167 -0
- iatp/tests/test_telemetry.py +175 -0
- mcp_kernel_server/__init__.py +28 -0
- mcp_kernel_server/cli.py +274 -0
- mcp_kernel_server/resources.py +217 -0
- mcp_kernel_server/server.py +564 -0
- mcp_kernel_server/tools.py +1174 -0
- mute_agent/__init__.py +68 -0
- mute_agent/core/__init__.py +1 -0
- mute_agent/core/execution_agent.py +166 -0
- mute_agent/core/handshake_protocol.py +201 -0
- mute_agent/core/reasoning_agent.py +238 -0
- mute_agent/knowledge_graph/__init__.py +1 -0
- mute_agent/knowledge_graph/graph_elements.py +65 -0
- mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
- mute_agent/knowledge_graph/subgraph.py +224 -0
- mute_agent/listener/__init__.py +43 -0
- mute_agent/listener/adapters/__init__.py +31 -0
- mute_agent/listener/adapters/base_adapter.py +189 -0
- mute_agent/listener/adapters/caas_adapter.py +344 -0
- mute_agent/listener/adapters/control_plane_adapter.py +436 -0
- mute_agent/listener/adapters/iatp_adapter.py +332 -0
- mute_agent/listener/adapters/scak_adapter.py +251 -0
- mute_agent/listener/listener.py +610 -0
- mute_agent/listener/state_observer.py +436 -0
- mute_agent/listener/threshold_config.py +313 -0
- mute_agent/super_system/__init__.py +1 -0
- mute_agent/super_system/router.py +204 -0
- mute_agent/visualization/__init__.py +10 -0
- mute_agent/visualization/graph_debugger.py +502 -0
- nexus/README.md +60 -0
- nexus/__init__.py +51 -0
- nexus/arbiter.py +359 -0
- nexus/client.py +466 -0
- nexus/dmz.py +444 -0
- nexus/escrow.py +430 -0
- nexus/exceptions.py +286 -0
- nexus/pyproject.toml +36 -0
- nexus/registry.py +393 -0
- nexus/reputation.py +425 -0
- nexus/schemas/__init__.py +51 -0
- nexus/schemas/compliance.py +276 -0
- nexus/schemas/escrow.py +251 -0
- nexus/schemas/manifest.py +225 -0
- nexus/schemas/receipt.py +208 -0
- nexus/tests/__init__.py +0 -0
- nexus/tests/conftest.py +146 -0
- nexus/tests/test_arbiter.py +192 -0
- nexus/tests/test_dmz.py +194 -0
- nexus/tests/test_escrow.py +276 -0
- nexus/tests/test_exceptions.py +225 -0
- nexus/tests/test_registry.py +232 -0
- nexus/tests/test_reputation.py +328 -0
- nexus/tests/test_schemas.py +295 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Safe File Reader Tool.
|
|
5
|
+
|
|
6
|
+
Provides read-only file access with security controls:
|
|
7
|
+
- Path sandboxing (only read from allowed directories)
|
|
8
|
+
- No directory traversal (../ blocked)
|
|
9
|
+
- File size limits
|
|
10
|
+
- Extension filtering
|
|
11
|
+
- No symbolic link following outside sandbox
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Dict, List, Optional, Set
|
|
17
|
+
|
|
18
|
+
from atr.decorator import tool
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FileReaderTool:
|
|
22
|
+
"""
|
|
23
|
+
Safe file reader with path sandboxing.
|
|
24
|
+
|
|
25
|
+
Features:
|
|
26
|
+
- Read-only operations only
|
|
27
|
+
- Sandbox path enforcement (can't read outside allowed dirs)
|
|
28
|
+
- Directory traversal prevention
|
|
29
|
+
- File size limits
|
|
30
|
+
- Extension whitelisting
|
|
31
|
+
- Symlink safety
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
```python
|
|
35
|
+
reader = FileReaderTool(
|
|
36
|
+
sandbox_paths=["/data/docs", "/data/configs"],
|
|
37
|
+
allowed_extensions=[".txt", ".json", ".yaml", ".md"],
|
|
38
|
+
max_file_size=1_000_000 # 1MB
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Register with ATR
|
|
42
|
+
registry.register(reader.read_file)
|
|
43
|
+
registry.register(reader.list_directory)
|
|
44
|
+
|
|
45
|
+
# Use from agent
|
|
46
|
+
content = reader.read_file("/data/docs/readme.txt")
|
|
47
|
+
```
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
sandbox_paths: Optional[List[str]] = None,
|
|
53
|
+
allowed_extensions: Optional[List[str]] = None,
|
|
54
|
+
blocked_extensions: Optional[List[str]] = None,
|
|
55
|
+
max_file_size: int = 10_000_000, # 10MB
|
|
56
|
+
follow_symlinks: bool = False,
|
|
57
|
+
encoding: str = "utf-8"
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
Initialize file reader tool.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
sandbox_paths: List of allowed directory paths
|
|
64
|
+
allowed_extensions: Whitelist of file extensions (e.g., [".txt", ".json"])
|
|
65
|
+
blocked_extensions: Blacklist of extensions (e.g., [".exe", ".sh"])
|
|
66
|
+
max_file_size: Maximum file size to read in bytes
|
|
67
|
+
follow_symlinks: Whether to follow symbolic links
|
|
68
|
+
encoding: Default file encoding
|
|
69
|
+
"""
|
|
70
|
+
self.sandbox_paths: List[Path] = [
|
|
71
|
+
Path(p).resolve() for p in (sandbox_paths or [os.getcwd()])
|
|
72
|
+
]
|
|
73
|
+
self.allowed_extensions: Optional[Set[str]] = (
|
|
74
|
+
set(ext.lower() for ext in allowed_extensions) if allowed_extensions else None
|
|
75
|
+
)
|
|
76
|
+
self.blocked_extensions: Set[str] = set(
|
|
77
|
+
ext.lower() for ext in (blocked_extensions or [
|
|
78
|
+
".exe", ".dll", ".so", ".dylib",
|
|
79
|
+
".sh", ".bash", ".zsh", ".fish",
|
|
80
|
+
".bat", ".cmd", ".ps1",
|
|
81
|
+
".py", ".pyc", ".pyo",
|
|
82
|
+
".class", ".jar",
|
|
83
|
+
])
|
|
84
|
+
)
|
|
85
|
+
self.max_file_size = max_file_size
|
|
86
|
+
self.follow_symlinks = follow_symlinks
|
|
87
|
+
self.encoding = encoding
|
|
88
|
+
|
|
89
|
+
def _validate_path(self, path: str) -> Path:
|
|
90
|
+
"""Validate path is within sandbox and safe to access."""
|
|
91
|
+
# Convert to Path and resolve
|
|
92
|
+
file_path = Path(path)
|
|
93
|
+
|
|
94
|
+
# Don't resolve symlinks if not allowed
|
|
95
|
+
if self.follow_symlinks:
|
|
96
|
+
resolved = file_path.resolve()
|
|
97
|
+
else:
|
|
98
|
+
# Resolve parent but not the file itself if it's a symlink
|
|
99
|
+
resolved = file_path.parent.resolve() / file_path.name
|
|
100
|
+
if file_path.is_symlink():
|
|
101
|
+
raise ValueError(f"Symbolic links not allowed: {path}")
|
|
102
|
+
|
|
103
|
+
# Check for directory traversal attempts
|
|
104
|
+
path_str = str(path)
|
|
105
|
+
if ".." in path_str:
|
|
106
|
+
raise ValueError(f"Directory traversal not allowed: {path}")
|
|
107
|
+
|
|
108
|
+
# Check if within sandbox
|
|
109
|
+
in_sandbox = False
|
|
110
|
+
for sandbox in self.sandbox_paths:
|
|
111
|
+
try:
|
|
112
|
+
resolved.relative_to(sandbox)
|
|
113
|
+
in_sandbox = True
|
|
114
|
+
break
|
|
115
|
+
except ValueError:
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
if not in_sandbox:
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f"Path '{path}' is outside allowed directories. "
|
|
121
|
+
f"Allowed: {[str(p) for p in self.sandbox_paths]}"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return resolved
|
|
125
|
+
|
|
126
|
+
def _validate_extension(self, path: Path):
|
|
127
|
+
"""Validate file extension."""
|
|
128
|
+
ext = path.suffix.lower()
|
|
129
|
+
|
|
130
|
+
# Check blocked extensions
|
|
131
|
+
if ext in self.blocked_extensions:
|
|
132
|
+
raise ValueError(f"File extension '{ext}' is blocked")
|
|
133
|
+
|
|
134
|
+
# Check allowed extensions (if whitelist set)
|
|
135
|
+
if self.allowed_extensions and ext not in self.allowed_extensions:
|
|
136
|
+
raise ValueError(
|
|
137
|
+
f"File extension '{ext}' not allowed. "
|
|
138
|
+
f"Allowed: {', '.join(sorted(self.allowed_extensions))}"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
@tool(
|
|
142
|
+
name="read_file",
|
|
143
|
+
description="Read the contents of a text file",
|
|
144
|
+
tags=["file", "read", "safe"]
|
|
145
|
+
)
|
|
146
|
+
def read_file(
|
|
147
|
+
self,
|
|
148
|
+
path: str,
|
|
149
|
+
encoding: Optional[str] = None,
|
|
150
|
+
max_lines: Optional[int] = None
|
|
151
|
+
) -> Dict[str, Any]:
|
|
152
|
+
"""
|
|
153
|
+
Read a file's contents.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
path: Path to file (must be within sandbox)
|
|
157
|
+
encoding: File encoding (default: utf-8)
|
|
158
|
+
max_lines: Maximum number of lines to read
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Dict with content, size, and metadata
|
|
162
|
+
"""
|
|
163
|
+
# Validate path
|
|
164
|
+
file_path = self._validate_path(path)
|
|
165
|
+
self._validate_extension(file_path)
|
|
166
|
+
|
|
167
|
+
# Check file exists
|
|
168
|
+
if not file_path.exists():
|
|
169
|
+
raise FileNotFoundError(f"File not found: {path}")
|
|
170
|
+
|
|
171
|
+
if not file_path.is_file():
|
|
172
|
+
raise ValueError(f"Path is not a file: {path}")
|
|
173
|
+
|
|
174
|
+
# Check file size
|
|
175
|
+
file_size = file_path.stat().st_size
|
|
176
|
+
if file_size > self.max_file_size:
|
|
177
|
+
raise ValueError(
|
|
178
|
+
f"File too large: {file_size} bytes. "
|
|
179
|
+
f"Maximum: {self.max_file_size} bytes"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Read file
|
|
183
|
+
enc = encoding or self.encoding
|
|
184
|
+
try:
|
|
185
|
+
content = file_path.read_text(encoding=enc)
|
|
186
|
+
except UnicodeDecodeError:
|
|
187
|
+
raise ValueError(f"Unable to decode file with encoding '{enc}'")
|
|
188
|
+
|
|
189
|
+
# Apply line limit
|
|
190
|
+
if max_lines:
|
|
191
|
+
lines = content.splitlines(keepends=True)
|
|
192
|
+
if len(lines) > max_lines:
|
|
193
|
+
content = "".join(lines[:max_lines])
|
|
194
|
+
content += f"\n... [truncated, showing {max_lines} of {len(lines)} lines]"
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
"content": content,
|
|
198
|
+
"size": file_size,
|
|
199
|
+
"path": str(file_path),
|
|
200
|
+
"encoding": enc,
|
|
201
|
+
"lines": content.count("\n") + 1
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@tool(
|
|
205
|
+
name="read_file_lines",
|
|
206
|
+
description="Read specific lines from a file",
|
|
207
|
+
tags=["file", "read", "safe"]
|
|
208
|
+
)
|
|
209
|
+
def read_lines(
|
|
210
|
+
self,
|
|
211
|
+
path: str,
|
|
212
|
+
start_line: int = 1,
|
|
213
|
+
end_line: Optional[int] = None,
|
|
214
|
+
encoding: Optional[str] = None
|
|
215
|
+
) -> Dict[str, Any]:
|
|
216
|
+
"""
|
|
217
|
+
Read specific lines from a file.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
path: Path to file
|
|
221
|
+
start_line: First line to read (1-indexed)
|
|
222
|
+
end_line: Last line to read (inclusive, None for end of file)
|
|
223
|
+
encoding: File encoding
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Dict with lines, content, and metadata
|
|
227
|
+
"""
|
|
228
|
+
file_path = self._validate_path(path)
|
|
229
|
+
self._validate_extension(file_path)
|
|
230
|
+
|
|
231
|
+
if not file_path.exists():
|
|
232
|
+
raise FileNotFoundError(f"File not found: {path}")
|
|
233
|
+
|
|
234
|
+
if start_line < 1:
|
|
235
|
+
raise ValueError("start_line must be >= 1")
|
|
236
|
+
|
|
237
|
+
enc = encoding or self.encoding
|
|
238
|
+
lines = file_path.read_text(encoding=enc).splitlines()
|
|
239
|
+
|
|
240
|
+
# Adjust for 0-indexing
|
|
241
|
+
start_idx = start_line - 1
|
|
242
|
+
end_idx = end_line if end_line else len(lines)
|
|
243
|
+
|
|
244
|
+
selected = lines[start_idx:end_idx]
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
"lines": selected,
|
|
248
|
+
"content": "\n".join(selected),
|
|
249
|
+
"start_line": start_line,
|
|
250
|
+
"end_line": min(end_idx, len(lines)),
|
|
251
|
+
"total_lines": len(lines),
|
|
252
|
+
"path": str(file_path)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
@tool(
|
|
256
|
+
name="list_directory",
|
|
257
|
+
description="List files and directories in a path",
|
|
258
|
+
tags=["file", "directory", "safe"]
|
|
259
|
+
)
|
|
260
|
+
def list_directory(
|
|
261
|
+
self,
|
|
262
|
+
path: str,
|
|
263
|
+
pattern: str = "*",
|
|
264
|
+
recursive: bool = False,
|
|
265
|
+
include_hidden: bool = False
|
|
266
|
+
) -> Dict[str, Any]:
|
|
267
|
+
"""
|
|
268
|
+
List directory contents.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
path: Directory path
|
|
272
|
+
pattern: Glob pattern (e.g., "*.txt")
|
|
273
|
+
recursive: Whether to search recursively
|
|
274
|
+
include_hidden: Include hidden files (starting with .)
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
Dict with files, directories, and counts
|
|
278
|
+
"""
|
|
279
|
+
dir_path = self._validate_path(path)
|
|
280
|
+
|
|
281
|
+
if not dir_path.exists():
|
|
282
|
+
raise FileNotFoundError(f"Directory not found: {path}")
|
|
283
|
+
|
|
284
|
+
if not dir_path.is_dir():
|
|
285
|
+
raise ValueError(f"Path is not a directory: {path}")
|
|
286
|
+
|
|
287
|
+
# List contents
|
|
288
|
+
if recursive:
|
|
289
|
+
matches = list(dir_path.rglob(pattern))
|
|
290
|
+
else:
|
|
291
|
+
matches = list(dir_path.glob(pattern))
|
|
292
|
+
|
|
293
|
+
files = []
|
|
294
|
+
directories = []
|
|
295
|
+
|
|
296
|
+
for item in matches:
|
|
297
|
+
# Skip hidden files unless requested
|
|
298
|
+
if not include_hidden and item.name.startswith("."):
|
|
299
|
+
continue
|
|
300
|
+
|
|
301
|
+
# Skip symlinks if not following
|
|
302
|
+
if item.is_symlink() and not self.follow_symlinks:
|
|
303
|
+
continue
|
|
304
|
+
|
|
305
|
+
rel_path = str(item.relative_to(dir_path))
|
|
306
|
+
|
|
307
|
+
if item.is_file():
|
|
308
|
+
files.append({
|
|
309
|
+
"name": item.name,
|
|
310
|
+
"path": rel_path,
|
|
311
|
+
"size": item.stat().st_size,
|
|
312
|
+
"extension": item.suffix
|
|
313
|
+
})
|
|
314
|
+
elif item.is_dir():
|
|
315
|
+
directories.append({
|
|
316
|
+
"name": item.name,
|
|
317
|
+
"path": rel_path
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
"path": str(dir_path),
|
|
322
|
+
"files": files,
|
|
323
|
+
"directories": directories,
|
|
324
|
+
"file_count": len(files),
|
|
325
|
+
"directory_count": len(directories)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
@tool(
|
|
329
|
+
name="file_exists",
|
|
330
|
+
description="Check if a file or directory exists",
|
|
331
|
+
tags=["file", "check", "safe"]
|
|
332
|
+
)
|
|
333
|
+
def exists(self, path: str) -> Dict[str, Any]:
|
|
334
|
+
"""
|
|
335
|
+
Check if path exists.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
path: Path to check
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
Dict with exists, is_file, is_dir
|
|
342
|
+
"""
|
|
343
|
+
try:
|
|
344
|
+
file_path = self._validate_path(path)
|
|
345
|
+
return {
|
|
346
|
+
"exists": file_path.exists(),
|
|
347
|
+
"is_file": file_path.is_file(),
|
|
348
|
+
"is_directory": file_path.is_dir(),
|
|
349
|
+
"path": str(file_path)
|
|
350
|
+
}
|
|
351
|
+
except ValueError:
|
|
352
|
+
# Path outside sandbox
|
|
353
|
+
return {
|
|
354
|
+
"exists": False,
|
|
355
|
+
"is_file": False,
|
|
356
|
+
"is_directory": False,
|
|
357
|
+
"path": path,
|
|
358
|
+
"error": "Path outside allowed directories"
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
@tool(
|
|
362
|
+
name="file_info",
|
|
363
|
+
description="Get metadata about a file",
|
|
364
|
+
tags=["file", "metadata", "safe"]
|
|
365
|
+
)
|
|
366
|
+
def file_info(self, path: str) -> Dict[str, Any]:
|
|
367
|
+
"""
|
|
368
|
+
Get file metadata.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
path: Path to file
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
Dict with size, modified time, etc.
|
|
375
|
+
"""
|
|
376
|
+
file_path = self._validate_path(path)
|
|
377
|
+
|
|
378
|
+
if not file_path.exists():
|
|
379
|
+
raise FileNotFoundError(f"File not found: {path}")
|
|
380
|
+
|
|
381
|
+
stat = file_path.stat()
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
"path": str(file_path),
|
|
385
|
+
"name": file_path.name,
|
|
386
|
+
"extension": file_path.suffix,
|
|
387
|
+
"size": stat.st_size,
|
|
388
|
+
"size_human": self._human_size(stat.st_size),
|
|
389
|
+
"modified": stat.st_mtime,
|
|
390
|
+
"created": stat.st_ctime,
|
|
391
|
+
"is_file": file_path.is_file(),
|
|
392
|
+
"is_directory": file_path.is_dir(),
|
|
393
|
+
"is_symlink": file_path.is_symlink()
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
def _human_size(self, size: int) -> str:
|
|
397
|
+
"""Convert bytes to human readable string."""
|
|
398
|
+
for unit in ["B", "KB", "MB", "GB"]:
|
|
399
|
+
if size < 1024:
|
|
400
|
+
return f"{size:.1f} {unit}"
|
|
401
|
+
size /= 1024
|
|
402
|
+
return f"{size:.1f} TB"
|