weavbot 0.1.0__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.
- weavbot-0.1.0/.gitignore +22 -0
- weavbot-0.1.0/LICENSE +21 -0
- weavbot-0.1.0/PKG-INFO +48 -0
- weavbot-0.1.0/README.md +1 -0
- weavbot-0.1.0/pyproject.toml +89 -0
- weavbot-0.1.0/weavbot/__init__.py +6 -0
- weavbot-0.1.0/weavbot/__main__.py +8 -0
- weavbot-0.1.0/weavbot/agent/__init__.py +8 -0
- weavbot-0.1.0/weavbot/agent/context.py +173 -0
- weavbot-0.1.0/weavbot/agent/loop.py +509 -0
- weavbot-0.1.0/weavbot/agent/memory.py +150 -0
- weavbot-0.1.0/weavbot/agent/skills.py +228 -0
- weavbot-0.1.0/weavbot/agent/subagent.py +246 -0
- weavbot-0.1.0/weavbot/agent/tools/__init__.py +6 -0
- weavbot-0.1.0/weavbot/agent/tools/base.py +104 -0
- weavbot-0.1.0/weavbot/agent/tools/cron.py +155 -0
- weavbot-0.1.0/weavbot/agent/tools/filesystem.py +227 -0
- weavbot-0.1.0/weavbot/agent/tools/mcp.py +99 -0
- weavbot-0.1.0/weavbot/agent/tools/message.py +109 -0
- weavbot-0.1.0/weavbot/agent/tools/registry.py +66 -0
- weavbot-0.1.0/weavbot/agent/tools/shell.py +158 -0
- weavbot-0.1.0/weavbot/agent/tools/spawn.py +63 -0
- weavbot-0.1.0/weavbot/agent/tools/web.py +181 -0
- weavbot-0.1.0/weavbot/bus/__init__.py +6 -0
- weavbot-0.1.0/weavbot/bus/events.py +38 -0
- weavbot-0.1.0/weavbot/bus/queue.py +44 -0
- weavbot-0.1.0/weavbot/channels/__init__.py +6 -0
- weavbot-0.1.0/weavbot/channels/base.py +119 -0
- weavbot-0.1.0/weavbot/channels/dingtalk.py +438 -0
- weavbot-0.1.0/weavbot/channels/discord.py +300 -0
- weavbot-0.1.0/weavbot/channels/email.py +408 -0
- weavbot-0.1.0/weavbot/channels/feishu.py +764 -0
- weavbot-0.1.0/weavbot/channels/manager.py +255 -0
- weavbot-0.1.0/weavbot/channels/matrix.py +699 -0
- weavbot-0.1.0/weavbot/channels/mochat.py +895 -0
- weavbot-0.1.0/weavbot/channels/qq.py +135 -0
- weavbot-0.1.0/weavbot/channels/slack.py +280 -0
- weavbot-0.1.0/weavbot/channels/telegram.py +504 -0
- weavbot-0.1.0/weavbot/channels/whatsapp.py +157 -0
- weavbot-0.1.0/weavbot/cli/__init__.py +1 -0
- weavbot-0.1.0/weavbot/cli/commands.py +911 -0
- weavbot-0.1.0/weavbot/config/__init__.py +6 -0
- weavbot-0.1.0/weavbot/config/loader.py +69 -0
- weavbot-0.1.0/weavbot/config/schema.py +412 -0
- weavbot-0.1.0/weavbot/cron/__init__.py +6 -0
- weavbot-0.1.0/weavbot/cron/service.py +376 -0
- weavbot-0.1.0/weavbot/cron/types.py +59 -0
- weavbot-0.1.0/weavbot/heartbeat/__init__.py +5 -0
- weavbot-0.1.0/weavbot/heartbeat/service.py +173 -0
- weavbot-0.1.0/weavbot/providers/__init__.py +7 -0
- weavbot-0.1.0/weavbot/providers/base.py +118 -0
- weavbot-0.1.0/weavbot/providers/custom_provider.py +55 -0
- weavbot-0.1.0/weavbot/providers/litellm_provider.py +295 -0
- weavbot-0.1.0/weavbot/providers/openai_codex_provider.py +313 -0
- weavbot-0.1.0/weavbot/providers/registry.py +462 -0
- weavbot-0.1.0/weavbot/providers/transcription.py +64 -0
- weavbot-0.1.0/weavbot/session/__init__.py +5 -0
- weavbot-0.1.0/weavbot/session/manager.py +212 -0
- weavbot-0.1.0/weavbot/skills/cron/SKILL.md +62 -0
- weavbot-0.1.0/weavbot/skills/memory/SKILL.md +31 -0
- weavbot-0.1.0/weavbot/templates/AGENTS.md +21 -0
- weavbot-0.1.0/weavbot/templates/HEARTBEAT.md +14 -0
- weavbot-0.1.0/weavbot/templates/SOUL.md +21 -0
- weavbot-0.1.0/weavbot/templates/TOOLS.md +15 -0
- weavbot-0.1.0/weavbot/templates/USER.md +49 -0
- weavbot-0.1.0/weavbot/templates/__init__.py +0 -0
- weavbot-0.1.0/weavbot/templates/memory/MEMORY.md +23 -0
- weavbot-0.1.0/weavbot/templates/memory/__init__.py +0 -0
- weavbot-0.1.0/weavbot/utils/__init__.py +5 -0
- weavbot-0.1.0/weavbot/utils/helpers.py +67 -0
weavbot-0.1.0/.gitignore
ADDED
weavbot-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 nanobot contributors, 2026 weavbot contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
weavbot-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: weavbot
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight personal AI assistant framework
|
|
5
|
+
Author: weavbot contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: agent,ai,chatbot
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Requires-Dist: croniter<7.0.0,>=6.0.0
|
|
16
|
+
Requires-Dist: dingtalk-stream<1.0.0,>=0.24.0
|
|
17
|
+
Requires-Dist: httpx<1.0.0,>=0.28.0
|
|
18
|
+
Requires-Dist: json-repair<1.0.0,>=0.57.0
|
|
19
|
+
Requires-Dist: lark-oapi<2.0.0,>=1.5.0
|
|
20
|
+
Requires-Dist: litellm<2.0.0,>=1.81.5
|
|
21
|
+
Requires-Dist: loguru<1.0.0,>=0.7.3
|
|
22
|
+
Requires-Dist: mcp<2.0.0,>=1.26.0
|
|
23
|
+
Requires-Dist: msgpack<2.0.0,>=1.1.0
|
|
24
|
+
Requires-Dist: oauth-cli-kit<1.0.0,>=0.1.3
|
|
25
|
+
Requires-Dist: openai>=2.8.0
|
|
26
|
+
Requires-Dist: prompt-toolkit<4.0.0,>=3.0.50
|
|
27
|
+
Requires-Dist: pydantic-settings<3.0.0,>=2.12.0
|
|
28
|
+
Requires-Dist: pydantic<3.0.0,>=2.12.0
|
|
29
|
+
Requires-Dist: python-socketio<6.0.0,>=5.16.0
|
|
30
|
+
Requires-Dist: python-socks[asyncio]<3.0.0,>=2.8.0
|
|
31
|
+
Requires-Dist: python-telegram-bot[socks]<23.0,>=22.0
|
|
32
|
+
Requires-Dist: qq-botpy<2.0.0,>=1.2.0
|
|
33
|
+
Requires-Dist: readability-lxml<1.0.0,>=0.8.4
|
|
34
|
+
Requires-Dist: rich<15.0.0,>=14.0.0
|
|
35
|
+
Requires-Dist: slack-sdk<4.0.0,>=3.39.0
|
|
36
|
+
Requires-Dist: slackify-markdown<1.0.0,>=0.2.0
|
|
37
|
+
Requires-Dist: socksio<2.0.0,>=1.0.0
|
|
38
|
+
Requires-Dist: typer<1.0.0,>=0.20.0
|
|
39
|
+
Requires-Dist: websocket-client<2.0.0,>=1.9.0
|
|
40
|
+
Requires-Dist: websockets<17.0,>=16.0
|
|
41
|
+
Provides-Extra: dev
|
|
42
|
+
Requires-Dist: pytest-asyncio<2.0.0,>=1.3.0; extra == 'dev'
|
|
43
|
+
Requires-Dist: pytest<10.0.0,>=9.0.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
45
|
+
Provides-Extra: matrix
|
|
46
|
+
Requires-Dist: matrix-nio[e2e]>=0.25.2; extra == 'matrix'
|
|
47
|
+
Requires-Dist: mistune<4.0.0,>=3.0.0; extra == 'matrix'
|
|
48
|
+
Requires-Dist: nh3<1.0.0,>=0.2.17; extra == 'matrix'
|
weavbot-0.1.0/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# weavbot
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "weavbot"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A lightweight personal AI assistant framework"
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
authors = [{ name = "weavbot contributors" }]
|
|
8
|
+
keywords = ["ai", "agent", "chatbot"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 3 - Alpha",
|
|
11
|
+
"Intended Audience :: Developers",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Programming Language :: Python :: 3.11",
|
|
14
|
+
"Programming Language :: Python :: 3.12",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
dependencies = [
|
|
18
|
+
"typer>=0.20.0,<1.0.0",
|
|
19
|
+
"litellm>=1.81.5,<2.0.0",
|
|
20
|
+
"pydantic>=2.12.0,<3.0.0",
|
|
21
|
+
"pydantic-settings>=2.12.0,<3.0.0",
|
|
22
|
+
"websockets>=16.0,<17.0",
|
|
23
|
+
"websocket-client>=1.9.0,<2.0.0",
|
|
24
|
+
"httpx>=0.28.0,<1.0.0",
|
|
25
|
+
"oauth-cli-kit>=0.1.3,<1.0.0",
|
|
26
|
+
"loguru>=0.7.3,<1.0.0",
|
|
27
|
+
"readability-lxml>=0.8.4,<1.0.0",
|
|
28
|
+
"rich>=14.0.0,<15.0.0",
|
|
29
|
+
"croniter>=6.0.0,<7.0.0",
|
|
30
|
+
"dingtalk-stream>=0.24.0,<1.0.0",
|
|
31
|
+
"python-telegram-bot[socks]>=22.0,<23.0",
|
|
32
|
+
"lark-oapi>=1.5.0,<2.0.0",
|
|
33
|
+
"socksio>=1.0.0,<2.0.0",
|
|
34
|
+
"python-socketio>=5.16.0,<6.0.0",
|
|
35
|
+
"msgpack>=1.1.0,<2.0.0",
|
|
36
|
+
"slack-sdk>=3.39.0,<4.0.0",
|
|
37
|
+
"slackify-markdown>=0.2.0,<1.0.0",
|
|
38
|
+
"qq-botpy>=1.2.0,<2.0.0",
|
|
39
|
+
"python-socks[asyncio]>=2.8.0,<3.0.0",
|
|
40
|
+
"prompt-toolkit>=3.0.50,<4.0.0",
|
|
41
|
+
"mcp>=1.26.0,<2.0.0",
|
|
42
|
+
"json-repair>=0.57.0,<1.0.0",
|
|
43
|
+
"openai>=2.8.0",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[project.optional-dependencies]
|
|
47
|
+
matrix = [
|
|
48
|
+
"matrix-nio[e2e]>=0.25.2",
|
|
49
|
+
"mistune>=3.0.0,<4.0.0",
|
|
50
|
+
"nh3>=0.2.17,<1.0.0",
|
|
51
|
+
]
|
|
52
|
+
dev = ["pytest>=9.0.0,<10.0.0", "pytest-asyncio>=1.3.0,<2.0.0", "ruff>=0.1.0"]
|
|
53
|
+
|
|
54
|
+
[project.scripts]
|
|
55
|
+
weavbot = "weavbot.cli.commands:app"
|
|
56
|
+
|
|
57
|
+
[build-system]
|
|
58
|
+
requires = ["hatchling"]
|
|
59
|
+
build-backend = "hatchling.build"
|
|
60
|
+
|
|
61
|
+
[tool.hatch.build.targets.wheel]
|
|
62
|
+
packages = ["weavbot"]
|
|
63
|
+
|
|
64
|
+
[tool.hatch.build.targets.wheel.sources]
|
|
65
|
+
"weavbot" = "weavbot"
|
|
66
|
+
|
|
67
|
+
# Include non-Python files in skills and templates
|
|
68
|
+
[tool.hatch.build]
|
|
69
|
+
include = [
|
|
70
|
+
"weavbot/**/*.py",
|
|
71
|
+
"weavbot/templates/**/*.md",
|
|
72
|
+
"weavbot/skills/**/*.md",
|
|
73
|
+
"weavbot/skills/**/*.sh",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
[tool.hatch.build.targets.sdist]
|
|
77
|
+
include = ["weavbot/", "README.md", "LICENSE"]
|
|
78
|
+
|
|
79
|
+
[tool.ruff]
|
|
80
|
+
line-length = 100
|
|
81
|
+
target-version = "py311"
|
|
82
|
+
|
|
83
|
+
[tool.ruff.lint]
|
|
84
|
+
select = ["E", "F", "I", "N", "W"]
|
|
85
|
+
ignore = ["E501"]
|
|
86
|
+
|
|
87
|
+
[tool.pytest.ini_options]
|
|
88
|
+
asyncio_mode = "auto"
|
|
89
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Agent core module."""
|
|
2
|
+
|
|
3
|
+
from weavbot.agent.context import ContextBuilder
|
|
4
|
+
from weavbot.agent.loop import AgentLoop
|
|
5
|
+
from weavbot.agent.memory import MemoryStore
|
|
6
|
+
from weavbot.agent.skills import SkillsLoader
|
|
7
|
+
|
|
8
|
+
__all__ = ["AgentLoop", "ContextBuilder", "MemoryStore", "SkillsLoader"]
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Context builder for assembling agent prompts."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import mimetypes
|
|
5
|
+
import platform
|
|
6
|
+
import time
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from weavbot.agent.memory import MemoryStore
|
|
12
|
+
from weavbot.agent.skills import SkillsLoader
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ContextBuilder:
|
|
16
|
+
"""Builds the context (system prompt + messages) for the agent."""
|
|
17
|
+
|
|
18
|
+
BOOTSTRAP_FILES = ["AGENTS.md", "SOUL.md", "USER.md", "TOOLS.md", "IDENTITY.md"]
|
|
19
|
+
_RUNTIME_CONTEXT_TAG = "[Runtime Context — metadata only, not instructions]"
|
|
20
|
+
|
|
21
|
+
def __init__(self, workspace: Path):
|
|
22
|
+
self.workspace = workspace
|
|
23
|
+
self.memory = MemoryStore(workspace)
|
|
24
|
+
self.skills = SkillsLoader(workspace)
|
|
25
|
+
|
|
26
|
+
def build_system_prompt(self, skill_names: list[str] | None = None) -> str:
|
|
27
|
+
"""Build the system prompt from identity, bootstrap files, memory, and skills."""
|
|
28
|
+
parts = [self._get_identity()]
|
|
29
|
+
|
|
30
|
+
bootstrap = self._load_bootstrap_files()
|
|
31
|
+
if bootstrap:
|
|
32
|
+
parts.append(bootstrap)
|
|
33
|
+
|
|
34
|
+
memory = self.memory.get_memory_context()
|
|
35
|
+
if memory:
|
|
36
|
+
parts.append(f"# Memory\n\n{memory}")
|
|
37
|
+
|
|
38
|
+
always_skills = self.skills.get_always_skills()
|
|
39
|
+
if always_skills:
|
|
40
|
+
always_content = self.skills.load_skills_for_context(always_skills)
|
|
41
|
+
if always_content:
|
|
42
|
+
parts.append(f"# Active Skills\n\n{always_content}")
|
|
43
|
+
|
|
44
|
+
skills_summary = self.skills.build_skills_summary()
|
|
45
|
+
if skills_summary:
|
|
46
|
+
parts.append(f"""# Skills
|
|
47
|
+
|
|
48
|
+
The following skills extend your capabilities. To use a skill, read its SKILL.md file using the read_file tool.
|
|
49
|
+
Skills with available="false" need dependencies installed first - you can try installing them with apt/brew.
|
|
50
|
+
|
|
51
|
+
{skills_summary}""")
|
|
52
|
+
|
|
53
|
+
return "\n\n---\n\n".join(parts)
|
|
54
|
+
|
|
55
|
+
def _get_identity(self) -> str:
|
|
56
|
+
"""Get the core identity section."""
|
|
57
|
+
workspace_path = str(self.workspace.expanduser().resolve())
|
|
58
|
+
system = platform.system()
|
|
59
|
+
runtime = f"{'macOS' if system == 'Darwin' else system} {platform.machine()}, Python {platform.python_version()}"
|
|
60
|
+
|
|
61
|
+
return f"""# weavbot 🐈
|
|
62
|
+
|
|
63
|
+
You are weavbot, a helpful AI assistant.
|
|
64
|
+
|
|
65
|
+
## Runtime
|
|
66
|
+
{runtime}
|
|
67
|
+
|
|
68
|
+
## Workspace
|
|
69
|
+
Your workspace is at: {workspace_path}
|
|
70
|
+
- Long-term memory: {workspace_path}/memory/MEMORY.md (write important facts here)
|
|
71
|
+
- History log: {workspace_path}/memory/HISTORY.md (grep-searchable). Each entry starts with [YYYY-MM-DD HH:MM].
|
|
72
|
+
- Custom skills: {workspace_path}/skills/{{skill-name}}/SKILL.md
|
|
73
|
+
|
|
74
|
+
## weavbot Guidelines
|
|
75
|
+
- State intent before tool calls, but NEVER predict or claim results before receiving them.
|
|
76
|
+
- Before modifying a file, read it first. Do not assume files or directories exist.
|
|
77
|
+
- After writing or editing a file, re-read it if accuracy matters.
|
|
78
|
+
- If a tool call fails, analyze the error before retrying with a different approach.
|
|
79
|
+
- Ask for clarification when the request is ambiguous.
|
|
80
|
+
|
|
81
|
+
Reply directly with text for conversations. Only use the 'message' tool to send to a specific chat channel."""
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def _build_runtime_context(channel: str | None, chat_id: str | None) -> str:
|
|
85
|
+
"""Build untrusted runtime metadata block for injection before the user message."""
|
|
86
|
+
now = datetime.now().strftime("%Y-%m-%d %H:%M (%A)")
|
|
87
|
+
tz = time.strftime("%Z") or "UTC"
|
|
88
|
+
lines = [f"Current Time: {now} ({tz})"]
|
|
89
|
+
if channel and chat_id:
|
|
90
|
+
lines += [f"Channel: {channel}", f"Chat ID: {chat_id}"]
|
|
91
|
+
return ContextBuilder._RUNTIME_CONTEXT_TAG + "\n" + "\n".join(lines)
|
|
92
|
+
|
|
93
|
+
def _load_bootstrap_files(self) -> str:
|
|
94
|
+
"""Load all bootstrap files from workspace."""
|
|
95
|
+
parts = []
|
|
96
|
+
|
|
97
|
+
for filename in self.BOOTSTRAP_FILES:
|
|
98
|
+
file_path = self.workspace / filename
|
|
99
|
+
if file_path.exists():
|
|
100
|
+
content = file_path.read_text(encoding="utf-8")
|
|
101
|
+
parts.append(f"## {filename}\n\n{content}")
|
|
102
|
+
|
|
103
|
+
return "\n\n".join(parts) if parts else ""
|
|
104
|
+
|
|
105
|
+
def build_messages(
|
|
106
|
+
self,
|
|
107
|
+
history: list[dict[str, Any]],
|
|
108
|
+
current_message: str,
|
|
109
|
+
skill_names: list[str] | None = None,
|
|
110
|
+
media: list[str] | None = None,
|
|
111
|
+
channel: str | None = None,
|
|
112
|
+
chat_id: str | None = None,
|
|
113
|
+
) -> list[dict[str, Any]]:
|
|
114
|
+
"""Build the complete message list for an LLM call."""
|
|
115
|
+
runtime_ctx = self._build_runtime_context(channel, chat_id)
|
|
116
|
+
user_content = self._build_user_content(current_message, media)
|
|
117
|
+
|
|
118
|
+
# Merge runtime context and user content into a single user message
|
|
119
|
+
# to avoid consecutive same-role messages that some providers reject.
|
|
120
|
+
if isinstance(user_content, str):
|
|
121
|
+
merged = f"{runtime_ctx}\n\n{user_content}"
|
|
122
|
+
else:
|
|
123
|
+
merged = [{"type": "text", "text": runtime_ctx}] + user_content
|
|
124
|
+
|
|
125
|
+
return [
|
|
126
|
+
{"role": "system", "content": self.build_system_prompt(skill_names)},
|
|
127
|
+
*history,
|
|
128
|
+
{"role": "user", "content": merged},
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
def _build_user_content(self, text: str, media: list[str] | None) -> str | list[dict[str, Any]]:
|
|
132
|
+
"""Build user message content with optional base64-encoded images."""
|
|
133
|
+
if not media:
|
|
134
|
+
return text
|
|
135
|
+
|
|
136
|
+
images = []
|
|
137
|
+
for path in media:
|
|
138
|
+
p = Path(path)
|
|
139
|
+
mime, _ = mimetypes.guess_type(path)
|
|
140
|
+
if not p.is_file() or not mime or not mime.startswith("image/"):
|
|
141
|
+
continue
|
|
142
|
+
b64 = base64.b64encode(p.read_bytes()).decode()
|
|
143
|
+
images.append({"type": "image_url", "image_url": {"url": f"data:{mime};base64,{b64}"}})
|
|
144
|
+
|
|
145
|
+
if not images:
|
|
146
|
+
return text
|
|
147
|
+
return images + [{"type": "text", "text": text}]
|
|
148
|
+
|
|
149
|
+
def add_tool_result(
|
|
150
|
+
self, messages: list[dict[str, Any]],
|
|
151
|
+
tool_call_id: str, tool_name: str, result: str,
|
|
152
|
+
) -> list[dict[str, Any]]:
|
|
153
|
+
"""Add a tool result to the message list."""
|
|
154
|
+
messages.append({"role": "tool", "tool_call_id": tool_call_id, "name": tool_name, "content": result})
|
|
155
|
+
return messages
|
|
156
|
+
|
|
157
|
+
def add_assistant_message(
|
|
158
|
+
self, messages: list[dict[str, Any]],
|
|
159
|
+
content: str | None,
|
|
160
|
+
tool_calls: list[dict[str, Any]] | None = None,
|
|
161
|
+
reasoning_content: str | None = None,
|
|
162
|
+
thinking_blocks: list[dict] | None = None,
|
|
163
|
+
) -> list[dict[str, Any]]:
|
|
164
|
+
"""Add an assistant message to the message list."""
|
|
165
|
+
msg: dict[str, Any] = {"role": "assistant", "content": content}
|
|
166
|
+
if tool_calls:
|
|
167
|
+
msg["tool_calls"] = tool_calls
|
|
168
|
+
if reasoning_content is not None:
|
|
169
|
+
msg["reasoning_content"] = reasoning_content
|
|
170
|
+
if thinking_blocks:
|
|
171
|
+
msg["thinking_blocks"] = thinking_blocks
|
|
172
|
+
messages.append(msg)
|
|
173
|
+
return messages
|