peteos 0.0.2__tar.gz

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.
Files changed (84) hide show
  1. peteos-0.0.2/LICENSE +60 -0
  2. peteos-0.0.2/MANIFEST.in +2 -0
  3. peteos-0.0.2/PKG-INFO +22 -0
  4. peteos-0.0.2/README.md +112 -0
  5. peteos-0.0.2/peteos/__init__.py +28 -0
  6. peteos-0.0.2/peteos/agentic_objects/__init__.py +20 -0
  7. peteos-0.0.2/peteos/agentic_objects/bash_workspace.py +219 -0
  8. peteos-0.0.2/peteos/agentic_objects/camera_driver/__init__.py +8 -0
  9. peteos-0.0.2/peteos/agentic_objects/camera_driver/camera_driver.py +50 -0
  10. peteos-0.0.2/peteos/agentic_objects/camera_driver/cv2_camera_driver.py +53 -0
  11. peteos-0.0.2/peteos/agentic_objects/camera_observer.py +131 -0
  12. peteos-0.0.2/peteos/agentic_objects/multi_camera_observer.py +133 -0
  13. peteos-0.0.2/peteos/agentic_objects/pdf_transcriber.py +202 -0
  14. peteos-0.0.2/peteos/agentic_objects/string_comparator.py +59 -0
  15. peteos-0.0.2/peteos/agentic_objects/text_editor.py +156 -0
  16. peteos-0.0.2/peteos/agentic_objects/web_navigator.py +302 -0
  17. peteos-0.0.2/peteos/channels/__init__.py +6 -0
  18. peteos-0.0.2/peteos/channels/nextcloud_talk_channel.py +635 -0
  19. peteos-0.0.2/peteos/channels/shell_channel.py +246 -0
  20. peteos-0.0.2/peteos/channels/stdout_channel.py +236 -0
  21. peteos-0.0.2/peteos/chatbot/__init__.py +20 -0
  22. peteos-0.0.2/peteos/chatbot/anthropicchatbot.py +427 -0
  23. peteos-0.0.2/peteos/chatbot/anthropicprovider.py +28 -0
  24. peteos-0.0.2/peteos/chatbot/backendconfig.py +82 -0
  25. peteos-0.0.2/peteos/chatbot/backendprovider.py +45 -0
  26. peteos-0.0.2/peteos/chatbot/chatbot.py +180 -0
  27. peteos-0.0.2/peteos/chatbot/chatbotconfig.py +59 -0
  28. peteos-0.0.2/peteos/chatbot/chatbotresponse.py +226 -0
  29. peteos-0.0.2/peteos/chatbot/geminichatbot.py +651 -0
  30. peteos-0.0.2/peteos/chatbot/geminiprovider.py +48 -0
  31. peteos-0.0.2/peteos/chatbot/httpclient.py +152 -0
  32. peteos-0.0.2/peteos/chatbot/manager.py +344 -0
  33. peteos-0.0.2/peteos/chatbot/openaichatbot.py +576 -0
  34. peteos-0.0.2/peteos/chatbot/openaiprovider.py +28 -0
  35. peteos-0.0.2/peteos/chatbot/response_types.py +38 -0
  36. peteos-0.0.2/peteos/chatbot/simplemock.py +101 -0
  37. peteos-0.0.2/peteos/config.py +136 -0
  38. peteos-0.0.2/peteos/conversation/__init__.py +27 -0
  39. peteos-0.0.2/peteos/conversation/context.py +660 -0
  40. peteos-0.0.2/peteos/conversation/media.py +256 -0
  41. peteos-0.0.2/peteos/conversation/message.py +357 -0
  42. peteos-0.0.2/peteos/conversation/message_registry.py +61 -0
  43. peteos-0.0.2/peteos/conversation/session.py +348 -0
  44. peteos-0.0.2/peteos/conversation/system_prompt_message.py +81 -0
  45. peteos-0.0.2/peteos/conversation/tool_definitions_message.py +167 -0
  46. peteos-0.0.2/peteos/engine/__init__.py +39 -0
  47. peteos-0.0.2/peteos/engine/channel.py +143 -0
  48. peteos-0.0.2/peteos/engine/exec_status.py +40 -0
  49. peteos-0.0.2/peteos/engine/executionenvironment.py +468 -0
  50. peteos-0.0.2/peteos/engine/runner.py +601 -0
  51. peteos-0.0.2/peteos/oap/__init__.py +14 -0
  52. peteos-0.0.2/peteos/oap/adaptive_object.py +292 -0
  53. peteos-0.0.2/peteos/oap/agentic_object.py +813 -0
  54. peteos-0.0.2/peteos/oap/agentic_registry.py +204 -0
  55. peteos-0.0.2/peteos/oap/decorators.py +68 -0
  56. peteos-0.0.2/peteos/oap/error.py +19 -0
  57. peteos-0.0.2/peteos/oap/prompts.py +33 -0
  58. peteos-0.0.2/peteos/oap/token_counter.py +69 -0
  59. peteos-0.0.2/peteos/persona/__init__.py +8 -0
  60. peteos-0.0.2/peteos/persona/agent.py +168 -0
  61. peteos-0.0.2/peteos/persona/role.py +206 -0
  62. peteos-0.0.2/peteos/persona/rolemanager.py +80 -0
  63. peteos-0.0.2/peteos/persona/toolmanager.py +181 -0
  64. peteos-0.0.2/peteos/sandbox/__init__.py +21 -0
  65. peteos-0.0.2/peteos/sandbox/sandbox.py +36 -0
  66. peteos-0.0.2/peteos/sandbox/sandbox_builder.py +686 -0
  67. peteos-0.0.2/peteos/sandbox/scope.py +19 -0
  68. peteos-0.0.2/peteos/utils/__init__.py +6 -0
  69. peteos-0.0.2/peteos/utils/_schema.py +497 -0
  70. peteos-0.0.2/peteos/utils/activeclass.py +154 -0
  71. peteos-0.0.2/peteos/utils/delta_merge.py +235 -0
  72. peteos-0.0.2/peteos/utils/dict_path.py +171 -0
  73. peteos-0.0.2/peteos/utils/json.py +42 -0
  74. peteos-0.0.2/peteos/utils/logger.py +89 -0
  75. peteos-0.0.2/peteos/utils/tiktoken.py +26 -0
  76. peteos-0.0.2/peteos.egg-info/PKG-INFO +22 -0
  77. peteos-0.0.2/peteos.egg-info/SOURCES.txt +82 -0
  78. peteos-0.0.2/peteos.egg-info/dependency_links.txt +1 -0
  79. peteos-0.0.2/peteos.egg-info/not-zip-safe +1 -0
  80. peteos-0.0.2/peteos.egg-info/requires.txt +18 -0
  81. peteos-0.0.2/peteos.egg-info/top_level.txt +1 -0
  82. peteos-0.0.2/pyproject.toml +27 -0
  83. peteos-0.0.2/setup.cfg +4 -0
  84. peteos-0.0.2/setup.py +14 -0
peteos-0.0.2/LICENSE ADDED
@@ -0,0 +1,60 @@
1
+ LICENSE
2
+ -------
3
+
4
+ Copyright (c) 2026 Schäfer List Systems GmbH
5
+
6
+ This software is dual-licensed.
7
+
8
+ 1. NON-COMMERCIAL LICENSE (FREE)
9
+
10
+ 1.1 Grant of License
11
+ Subject to the terms of this Section 1, you are granted a non-exclusive,
12
+ non-transferable, worldwide, royalty-free license to use, copy, modify,
13
+ and distribute this software, solely for non-commercial purposes.
14
+
15
+ 1.2 Definition of Non-Commercial Use
16
+ "Non-commercial use" means any use that is not primarily intended for or
17
+ directed towards commercial advantage or monetary compensation, including
18
+ but not limited to:
19
+ - personal projects,
20
+ - academic research,
21
+ - internal evaluation and testing,
22
+ - evaluation of potential commercial use cases within your organization.
23
+
24
+ 1.3 Evaluation of Commercial Use Cases
25
+ You may use this software under this non-commercial license to evaluate
26
+ its suitability for a commercial use case within your organization.
27
+ However, any actual deployment, production use, or integration into a
28
+ product or service that is offered commercially requires a separate
29
+ commercial license from Your Company.
30
+
31
+ 1.4 Restrictions
32
+ Under this non-commercial license you may NOT:
33
+ - use the software in any product or service offered for sale, license,
34
+ or subscription;
35
+ - use the software to provide any commercial hosting, SaaS, or managed
36
+ service;
37
+ - remove or alter copyright notices or license texts.
38
+
39
+ 1.5 No Warranty
40
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
41
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
42
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
43
+
44
+ 2. COMMERCIAL LICENSE
45
+
46
+ If you wish to use this software for any commercial purpose, including but
47
+ not limited to production deployment, integration into a commercial product,
48
+ or offering any service based on this software, you must obtain a commercial
49
+ license from Your Company.
50
+
51
+ For commercial licensing, please contact:
52
+ Email: info@schaeferlist.de
53
+ Web: https://www.schaeferlist.de
54
+
55
+ 3. GENERAL
56
+
57
+ This dual-licensing model is permissible because the Schaefer List Systems GmbH
58
+ owns all copyrights in this software. By using this software, you agree to comply
59
+ with either the non-commercial license (Section 1) or a valid commercial
60
+ license (Section 2), as applicable to your use case.
@@ -0,0 +1,2 @@
1
+ global-exclude *.c
2
+ global-exclude __pycache__
peteos-0.0.2/PKG-INFO ADDED
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.4
2
+ Name: peteos
3
+ Version: 0.0.2
4
+ Summary: Agentic application framework
5
+ Requires-Python: >=3.10
6
+ License-File: LICENSE
7
+ Requires-Dist: json5>=0.15.0
8
+ Requires-Dist: aiohttp>=3.9.0
9
+ Requires-Dist: httpx>=0.27.0
10
+ Requires-Dist: tiktoken>=0.7.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest; extra == "dev"
13
+ Requires-Dist: pytest-asyncio; extra == "dev"
14
+ Requires-Dist: setuptools; extra == "dev"
15
+ Requires-Dist: cython>=3.0; extra == "dev"
16
+ Provides-Extra: camera
17
+ Requires-Dist: opencv-python>=4.8.0; extra == "camera"
18
+ Requires-Dist: numpy>=1.24.0; extra == "camera"
19
+ Provides-Extra: web
20
+ Requires-Dist: beautifulsoup4; extra == "web"
21
+ Requires-Dist: lxml; extra == "web"
22
+ Dynamic: license-file
peteos-0.0.2/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # Peteos
2
+
3
+ Simple Object-Agentic Programming (sOAP) — an agentic application framework that brings together object-oriented programming and AI agents.
4
+ The easiest way to build reliable and powerful multi-agent systems.
5
+
6
+ Peteos lets you build **agentic objects**: ordinary Python objects that can think, decide, and act on their own.
7
+ No external memory stores or bolt-on intelligence — the object itself is the center of persistence and agency.
8
+
9
+ ## Quick Start
10
+
11
+ After installing and configuring Peteos, you can build and use agentic objects like the following.
12
+
13
+ ```python
14
+ class Example(AgenticObject):
15
+ """You are Pete, a concise assistant."""
16
+ def __init__(self, job: str):
17
+ super().__init__()
18
+ self._job = job
19
+
20
+ @tool
21
+ def get_job(self) -> str:
22
+ return self._job
23
+
24
+ pete = Example("demonstrator")
25
+ result = await pete.invoke_agent("Hello, what's your name and job?", output_schema=list[str])
26
+ print(result) # ['Pete', 'demonstrator']
27
+ ```
28
+
29
+ ## Key Features
30
+
31
+ The documentation and the examples cover more complex agentic objects with the following key features.
32
+
33
+ - **Agentic objects** — derive from `AgenticObject` and get a thinking agent behind every instance.
34
+ - **Inheritance** — inherit from agentic objects to refine the system prompt and extend its toolset.
35
+ - **Multi-inheritance composition** — combine agentic classes to compose behavior.
36
+ - **Structured output** — declare `output_schema` and get typed results back.
37
+ - **Sandboxed code execution** — let agents run Python in a restricted sandbox.
38
+ - **Persistent sessions** — threads, memory, and sub-agent coordination via `persistent_thread_id`.
39
+ - **Test-driven development** — it's OOP, so test your agents as you test any other software.
40
+ - **Debuggable agents** — it's plain Python, so debug your agents with a standard debugger.
41
+
42
+
43
+ ## Installation
44
+
45
+ ### From source
46
+
47
+ ```bash
48
+ git clone --recurse-submodules https://github.com/yourusername/peteos.git
49
+ cd peteos
50
+ python3 -m venv .venv
51
+ source .venv/bin/activate
52
+ pip install -e ".[dev]"
53
+ ```
54
+
55
+ ### From a distribution package
56
+
57
+ Download a `.tar.gz` archive and extract it, then install the wheel inside:
58
+
59
+ ```bash
60
+ tar xzf peteos-0.0.1.tar.gz
61
+ cd peteos-0.0.1
62
+ python -m venv .venv
63
+ source .venv/bin/activate
64
+ pip install peteos-0.0.1-cp312-cp312-linux_x86_64.whl
65
+ ```
66
+
67
+ ### From a Python package (PyPI / direct install)
68
+
69
+ ```bash
70
+ python -m venv .venv
71
+ source .venv/bin/activate
72
+ pip install peteos
73
+ ```
74
+
75
+ ## Configuration
76
+
77
+ Copy `peteos.json.example` to `peteos.json` and adapt it to your environment:
78
+
79
+ ```bash
80
+ cp peteos.json.example peteos.json
81
+ ```
82
+
83
+ The configuration file defines chatbot backends (LLM providers) that Peteos uses for agent reasoning:
84
+
85
+ - **name** — a descriptive label for the backend
86
+ - **url** — the API endpoint for the provider
87
+ - **api_type** — `anthropic`, `gemini`, or omitted for OpenAI-compatible endpoints
88
+ - **api_key** — environment variable name holding the API key (never hardcode keys)
89
+ - **model_priorities** — maps model names to priority scores (higher = preferred)
90
+
91
+ To verify that your configuration is functional run `python3 examples/00_hello_pete.py`.
92
+
93
+ ## Resources
94
+
95
+ - [Getting Started](docs/getting-started.md) — installation and first example
96
+ - [Introduction](docs/introduction.md) — the sOAP paradigm
97
+ - [Concepts](docs/concepts/index.md) — composition, invocation, state, testing
98
+ - [Reference](docs/reference/index.md) — API docs, agentic objects
99
+ - [Best Practices](docs/best-practices/index.md) — Best practices when using PeteOS
100
+ - [Examples](docs/examples/) — Examples show-casing some features
101
+
102
+ ## Licensing
103
+
104
+ This project is dual-licensed:
105
+
106
+ - **Non-commercial use** (including evaluation of commercial use cases) is free
107
+ under the license in the `LICENSE` file.
108
+ - **Commercial use** (production, products, services, SaaS, etc.) requires a
109
+ commercial license from the Schäfer List Systems GmbH.
110
+
111
+ If you intend to use this software commercially, please contact us at
112
+ <info@schaeferlist.de> or visit <https://www.schaeferlist.de>.
@@ -0,0 +1,28 @@
1
+ # Peteos - Agentic application framework
2
+
3
+ import asyncio
4
+
5
+ from peteos.config import ConfigManager
6
+
7
+ # OAP primitives
8
+ from peteos.oap import (
9
+ AgenticObject,
10
+ AdaptiveObject,
11
+ Error,
12
+ agentic_object,
13
+ tool,
14
+ )
15
+
16
+ __all__ = [
17
+ "AgenticObject",
18
+ "AdaptiveObject",
19
+ "Error",
20
+ "agentic_object",
21
+ "tool",
22
+ ]
23
+
24
+ # Bootstrap backends and roles from peteos.json at import time.
25
+ try:
26
+ asyncio.get_running_loop()
27
+ except RuntimeError:
28
+ asyncio.run(ConfigManager.init())
@@ -0,0 +1,20 @@
1
+ """Agentic Objects - concrete implementations using the OAP framework."""
2
+
3
+ from peteos.agentic_objects.camera_driver import CameraDriver, CV2CameraDriver
4
+ from peteos.agentic_objects.camera_observer import CameraObserver
5
+ from peteos.agentic_objects.multi_camera_observer import MultiCameraObserver
6
+ from peteos.agentic_objects.pdf_transcriber import PdfTranscriber
7
+ from peteos.agentic_objects.bash_workspace import BashWorkspace
8
+ from peteos.agentic_objects.string_comparator import AgenticStringComparator
9
+ from peteos.agentic_objects.text_editor import TextEditor
10
+
11
+ __all__ = [
12
+ "AgenticStringComparator",
13
+ "BashWorkspace",
14
+ "CameraDriver",
15
+ "CV2CameraDriver",
16
+ "CameraObserver",
17
+ "MultiCameraObserver",
18
+ "PdfTranscriber",
19
+ "TextEditor",
20
+ ]
@@ -0,0 +1,219 @@
1
+ """BashWorkspace - secure bash execution agentic object."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import subprocess
7
+ import tempfile
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING
10
+
11
+ from peteos.oap.agentic_object import AgenticObject
12
+ from peteos.oap.decorators import tool
13
+ from peteos.utils import get_logger
14
+
15
+ _logger = get_logger(__name__)
16
+
17
+ if TYPE_CHECKING:
18
+ from peteos.engine import Runner
19
+
20
+ # Safe POSIX utilities available in the sandbox.
21
+ # No network tools (curl, wget, nc), no shells (bash -c via env vars), no
22
+ # process control (kill, top), no system introspection (ps, whoami).
23
+ _SAFE_COMMANDS: set[str] = {
24
+ "cat", "echo", "grep", "egrep", "sed", "awk", "sort", "uniq", "wc",
25
+ "head", "tail", "cut", "tr", "mkdir", "cp", "mv", "rm", "ln", "chmod",
26
+ "touch", "find", "basename", "dirname", "test", "date", "sleep",
27
+ "tee", "xargs", "shuf", "paste", "join", "diff", "comm", "uniq",
28
+ "base64", "md5sum", "sha256sum", "stat", "du", "file", "ls",
29
+ # Bash builtins — executed via /bin/sh -c, so not listed here directly.
30
+ }
31
+
32
+ # Strictly minimal POSIX environment: no PATH leaks, no secrets, no user config.
33
+ _MINIMAL_ENV: dict[str, str] = {
34
+ "PATH": "/usr/bin:/bin",
35
+ "HOME": "/tmp/bash_workspace_home",
36
+ "LANG": "C",
37
+ "LC_ALL": "C",
38
+ "TERM": "dumb",
39
+ "TMPDIR": "/tmp",
40
+ }
41
+
42
+
43
+ class BashWorkspace(AgenticObject):
44
+ """You are a secure bash workspace for executing shell commands.
45
+
46
+ You can run bash commands on files placed in your workspace. You have
47
+ NO access to the rest of the filesystem beyond your workspace directory.
48
+ The environment is minimal: no network tools, no secrets, no user data.
49
+
50
+ Your workspace is created fresh for each invocation and destroyed
51
+ afterward. Files must be provided to you — you cannot access anything
52
+ outside your workspace directory.
53
+
54
+ Available commands: cat, echo, grep, sed, awk, sort, uniq, wc, head,
55
+ tail, cut, tr, mkdir, cp, mv, rm, ln, chmod, touch, find, basename,
56
+ dirname, test, date, sleep, tee, xargs, shuf, paste, join, diff, comm,
57
+ base64, md5sum, sha256sum, stat, du, file, ls.
58
+
59
+ No network tools (curl, wget, nc) or shell escapes are available.
60
+ """
61
+
62
+ def __init__(self) -> None:
63
+ super().__init__()
64
+ self._workspace_dir: Path | None = None
65
+
66
+ @property
67
+ def workspace_dir(self) -> Path | None:
68
+ """The current workspace directory, or None if not active."""
69
+ return self._workspace_dir
70
+
71
+ def _setup_workspace(self) -> Path:
72
+ """Create or return the workspace directory.
73
+
74
+ Uses an existing workspace if available, otherwise creates a new
75
+ temporary directory.
76
+ """
77
+ if self._workspace_dir is None or not self._workspace_dir.is_dir():
78
+ self._workspace_dir = Path(tempfile.mkdtemp(prefix="bash_workspace_"))
79
+ # Create HOME dir so tools like `ssh` (if it slipped through)
80
+ # don't error on missing ~/.ssh — this is a defense-in-depth.
81
+ (self._workspace_dir / "home").mkdir(exist_ok=True)
82
+ os.environ["HOME"] = str(self._workspace_dir / "home")
83
+ return self._workspace_dir
84
+
85
+ def _teardown_workspace(self) -> None:
86
+ """Remove the workspace directory."""
87
+ if self._workspace_dir and self._workspace_dir.is_dir():
88
+ import shutil
89
+ shutil.rmtree(self._workspace_dir, ignore_errors=True)
90
+ self._workspace_dir = None
91
+
92
+ @property
93
+ def _env(self) -> dict[str, str]:
94
+ """Build the sandbox environment with the workspace HOME."""
95
+ env = {**_MINIMAL_ENV}
96
+ if self._workspace_dir:
97
+ env["HOME"] = str(self._workspace_dir / "home")
98
+ # Inherit only a few safe env vars from the parent process.
99
+ for key in ("TERM", "LANG", "LC_ALL"):
100
+ val = os.environ.get(key)
101
+ if val:
102
+ env[key] = val
103
+ return env
104
+
105
+ def _is_safe_command(self, command: str) -> bool:
106
+ """Check if the command (first word) is in the allowlist."""
107
+ first = command.strip().split()[0] if command.strip() else ""
108
+ # Handle built-in /bin/sh -c wrapper
109
+ if first == "/bin/sh" or first == "sh":
110
+ return True
111
+ # Extract basename for /usr/bin/foo paths
112
+ base = os.path.basename(first)
113
+ return base in _SAFE_COMMANDS
114
+
115
+ @tool
116
+ def bash_exec(self, command: str, timeout: int = 30) -> str:
117
+ """Execute a bash command in the sandboxed workspace.
118
+
119
+ Args:
120
+ command: The bash command to execute.
121
+
122
+ Returns:
123
+ A string containing exit code, stdout, and stderr information.
124
+ """
125
+ ws = self._setup_workspace()
126
+
127
+ if not command.strip():
128
+ return "Error: Empty command."
129
+
130
+ # Block path traversal patterns in the raw command text before
131
+ # we even spawn a shell. This catches ../ escape attempts early.
132
+ if ".." in command:
133
+ return "Error: Path traversal is not allowed."
134
+
135
+ # Block command substitution and backticks that could bypass
136
+ # the allowlist via subshell execution.
137
+ _BLOCKED_CHARS = ("`", "$(")
138
+ for bad in _BLOCKED_CHARS:
139
+ if bad in command:
140
+ return f"Error: The character or sequence '{bad}' is not allowed."
141
+
142
+ # Quick allowlist check on the first token to catch obviously
143
+ # dangerous commands before we even spawn a shell.
144
+ if not self._is_safe_command(command):
145
+ base = os.path.basename(command.strip().split()[0])
146
+ return f"Error: Command '{base}' is not allowed in the sandbox."
147
+
148
+ _logger.debug("[bash_exec] ws=%s, command=%r", ws, command)
149
+ try:
150
+ result = subprocess.run(
151
+ ["/bin/sh", "-c", command],
152
+ cwd=ws,
153
+ env=self._env,
154
+ capture_output=True,
155
+ text=True,
156
+ timeout=timeout,
157
+ )
158
+ _logger.debug("[bash_exec] stdout=%r, stderr=%r, returncode=%d", result.stdout, result.stderr, result.returncode)
159
+ output = f"exit_code: {result.returncode}"
160
+ if result.stdout:
161
+ output += f"\nstdout:\n{result.stdout}"
162
+ if result.stderr:
163
+ output += f"\nstderr:\n{result.stderr}"
164
+ if not result.stdout and not result.stderr:
165
+ output = "(no output)"
166
+ return output
167
+ except subprocess.TimeoutExpired:
168
+ return "Error: Command timed out after 30 seconds."
169
+ except Exception as e:
170
+ return f"Error: {type(e).__name__}: {e}"
171
+
172
+ @tool
173
+ def put_file(self, name: str, content: str) -> str:
174
+ """Place a file into the workspace.
175
+
176
+ Args:
177
+ name: The filename to create in the workspace.
178
+ content: The file contents as a string.
179
+
180
+ Returns:
181
+ Confirmation message.
182
+ """
183
+ ws = self._setup_workspace()
184
+ filepath = (ws / name).resolve()
185
+ # Guard: prevent directory traversal outside workspace.
186
+ # resolve() normalizes .. so this is a proper containment check.
187
+ try:
188
+ filepath.relative_to(ws.resolve())
189
+ except ValueError:
190
+ return f"Error: File name '{name}' is not allowed."
191
+ try:
192
+ filepath.write_text(content)
193
+ return f"OK: File '{name}' successfully written."
194
+ except Exception as e:
195
+ return f"Error: Failed to write '{name}': {e}"
196
+
197
+ @tool
198
+ def get_file(self, name: str) -> str:
199
+ """Read the content of a file from the workspace.
200
+
201
+ Args:
202
+ name: The filename to read from the workspace.
203
+
204
+ Returns:
205
+ The file contents as a string.
206
+ """
207
+ ws = self._setup_workspace()
208
+ filepath = (ws / name).resolve()
209
+ # resolve() normalizes .. so this is a proper containment check.
210
+ try:
211
+ filepath.relative_to(ws.resolve())
212
+ except ValueError:
213
+ return f"Error: File name '{name}' is not allowed."
214
+ if not filepath.is_file():
215
+ return f"Error: File '{name}' not found in workspace."
216
+ try:
217
+ return filepath.read_text()
218
+ except Exception as e:
219
+ return f"Error: Failed to read '{name}': {e}"
@@ -0,0 +1,8 @@
1
+ """CameraDriver - abstract interface for camera backends."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from peteos.agentic_objects.camera_driver.camera_driver import CameraDriver
6
+ from peteos.agentic_objects.camera_driver.cv2_camera_driver import CV2CameraDriver
7
+
8
+ __all__ = ["CameraDriver", "CV2CameraDriver"]
@@ -0,0 +1,50 @@
1
+ """CameraDriver - abstract interface for camera backends."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+
7
+ import numpy as np
8
+
9
+
10
+ class CameraDriver(ABC):
11
+ """Interface for camera backends.
12
+
13
+ A driver is responsible for listing, opening, closing, and grabbing
14
+ frames from a specific camera subsystem (e.g. V4L2, USB, RTSP).
15
+ The driver returns raw numpy arrays; encoding/scaling is handled
16
+ by the observer.
17
+ """
18
+
19
+ driver_name: str = ""
20
+
21
+ @abstractmethod
22
+ def list_cameras(self) -> list[tuple[int, str]]:
23
+ """List available cameras.
24
+
25
+ Returns:
26
+ List of (camera_id, description) tuples.
27
+ """
28
+
29
+ @abstractmethod
30
+ def open(self, camera_id: int) -> bool:
31
+ """Open a camera for capturing.
32
+
33
+ Args:
34
+ camera_id: The camera ID from ``list_cameras``.
35
+
36
+ Returns:
37
+ True if the camera was opened successfully.
38
+ """
39
+
40
+ @abstractmethod
41
+ def close(self) -> None:
42
+ """Close the currently open camera."""
43
+
44
+ @abstractmethod
45
+ def grab_frame(self) -> tuple[bool, np.ndarray, tuple[int, int]]:
46
+ """Grab a single raw frame.
47
+
48
+ Returns:
49
+ Tuple of (success, numpy array, (width, height)).
50
+ """
@@ -0,0 +1,53 @@
1
+ """CV2CameraDriver - portable V4L2 camera driver via OpenCV.
2
+
3
+ Unlike OpenCVCameraDriver, this driver does not shell out to
4
+ v4l2-ctl. It simply tests indices 0-9 with OpenCV, making it
5
+ portable across Linux, Windows, and macOS.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import TYPE_CHECKING
11
+
12
+ import cv2
13
+ import numpy as np
14
+
15
+ from peteos.agentic_objects.camera_driver import CameraDriver
16
+
17
+ if TYPE_CHECKING:
18
+ pass
19
+
20
+
21
+ class CV2CameraDriver(CameraDriver):
22
+ """Portable V4L2 driver backed by OpenCV only."""
23
+
24
+ driver_name = "opencv-cv2"
25
+
26
+ def list_cameras(self) -> list[tuple[int, str]]:
27
+ cameras: list[tuple[int, str]] = []
28
+ for i in range(10):
29
+ cap = cv2.VideoCapture(i)
30
+ if cap.isOpened():
31
+ cameras.append((i, ""))
32
+ cap.release()
33
+ return cameras
34
+
35
+ def open(self, camera_id: int) -> bool:
36
+ cap = cv2.VideoCapture(camera_id)
37
+ if not cap.isOpened():
38
+ return False
39
+ self._cap = cap
40
+ return True
41
+
42
+ def close(self) -> None:
43
+ if hasattr(self, "_cap"):
44
+ self._cap.release()
45
+ del self._cap
46
+
47
+ def grab_frame(self) -> tuple[bool, np.ndarray, tuple[int, int]]:
48
+ if not hasattr(self, "_cap"):
49
+ return False, np.empty((0, 0, 3)), (0, 0)
50
+ ret, frame = self._cap.read()
51
+ if not ret or frame is None:
52
+ return False, np.empty((0, 0, 3)), (0, 0)
53
+ return True, frame, (frame.shape[1], frame.shape[0])