bub 0.3.2__tar.gz → 0.3.3__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.
- {bub-0.3.2 → bub-0.3.3}/PKG-INFO +26 -23
- {bub-0.3.2 → bub-0.3.3}/README.md +23 -22
- {bub-0.3.2 → bub-0.3.3}/pyproject.toml +6 -1
- {bub-0.3.2 → bub-0.3.3}/src/bub/__init__.py +1 -1
- {bub-0.3.2 → bub-0.3.3}/src/bub/__main__.py +14 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/builtin/agent.py +1 -3
- {bub-0.3.2 → bub-0.3.3}/src/bub/builtin/hook_impl.py +1 -1
- {bub-0.3.2 → bub-0.3.3}/src/bub/builtin/settings.py +2 -1
- {bub-0.3.2 → bub-0.3.3}/src/bub/builtin/shell_manager.py +2 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/builtin/store.py +3 -3
- {bub-0.3.2 → bub-0.3.3}/src/bub/builtin/tools.py +3 -1
- {bub-0.3.2 → bub-0.3.3}/tests/test_builtin_agent.py +9 -1
- {bub-0.3.2 → bub-0.3.3}/tests/test_framework.py +5 -5
- {bub-0.3.2 → bub-0.3.3}/tests/test_settings.py +23 -1
- bub-0.3.3/tests/test_tape_search_output.py +55 -0
- {bub-0.3.2 → bub-0.3.3}/LICENSE +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/builtin/__init__.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/builtin/auth.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/builtin/cli.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/builtin/context.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/builtin/tape.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/channels/__init__.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/channels/base.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/channels/cli/__init__.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/channels/cli/renderer.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/channels/handler.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/channels/manager.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/channels/message.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/channels/telegram.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/envelope.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/framework.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/hook_runtime.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/hookspecs.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/skills.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/tools.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/types.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/bub/utils.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/skills/README.md +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/skills/gh/SKILL.md +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/skills/skill-creator/SKILL.md +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/skills/skill-creator/license.txt +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/skills/skill-creator/scripts/init_skill.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/skills/skill-creator/scripts/quick_validate.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/skills/telegram/SKILL.md +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/skills/telegram/scripts/telegram_edit.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/src/skills/telegram/scripts/telegram_send.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/tests/test_builtin_cli.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/tests/test_builtin_hook_impl.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/tests/test_builtin_tools.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/tests/test_channels.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/tests/test_cli_help.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/tests/test_envelope.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/tests/test_file_tape_store_entry_ids.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/tests/test_fork_store_merge_back.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/tests/test_hook_runtime.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/tests/test_image_message.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/tests/test_skills.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/tests/test_subagent_tool.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/tests/test_tools.py +0 -0
- {bub-0.3.2 → bub-0.3.3}/tests/test_utils.py +0 -0
{bub-0.3.2 → bub-0.3.3}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: bub
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: A common shape for agents that live alongside people.
|
|
5
5
|
Author-Email: Chojan Shang <psiace@apache.org>, Frost Ming <me@frostming.com>, Yihong <zouzou0208@gmail.com>
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -27,6 +27,8 @@ Requires-Dist: python-telegram-bot>=21.0
|
|
|
27
27
|
Requires-Dist: loguru>=0.7.2
|
|
28
28
|
Requires-Dist: rapidfuzz>=3.14.3
|
|
29
29
|
Requires-Dist: aiohttp>=3.13.3
|
|
30
|
+
Provides-Extra: logfire
|
|
31
|
+
Requires-Dist: logfire>=4.31.0; extra == "logfire"
|
|
30
32
|
Description-Content-Type: text/markdown
|
|
31
33
|
|
|
32
34
|
# Bub
|
|
@@ -84,10 +86,10 @@ If `AGENTS.md` exists in the workspace, it is appended to the system prompt auto
|
|
|
84
86
|
|
|
85
87
|
Key source files:
|
|
86
88
|
|
|
87
|
-
- Turn orchestrator: [`src/bub/framework.py`](src/bub/framework.py)
|
|
88
|
-
- Hook contract: [`src/bub/hookspecs.py`](src/bub/hookspecs.py)
|
|
89
|
-
- Builtin hooks: [`src/bub/builtin/hook_impl.py`](src/bub/builtin/hook_impl.py)
|
|
90
|
-
- Skill discovery: [`src/bub/skills.py`](src/bub/skills.py)
|
|
89
|
+
- Turn orchestrator: [`src/bub/framework.py`](https://github.com/bubbuild/bub/blob/main/src/bub/framework.py)
|
|
90
|
+
- Hook contract: [`src/bub/hookspecs.py`](https://github.com/bubbuild/bub/blob/main/src/bub/hookspecs.py)
|
|
91
|
+
- Builtin hooks: [`src/bub/builtin/hook_impl.py`](https://github.com/bubbuild/bub/blob/main/src/bub/builtin/hook_impl.py)
|
|
92
|
+
- Skill discovery: [`src/bub/skills.py`](https://github.com/bubbuild/bub/blob/main/src/bub/skills.py)
|
|
91
93
|
|
|
92
94
|
## What Sets It Apart
|
|
93
95
|
|
|
@@ -134,15 +136,16 @@ Lines starting with `,` enter internal command mode (`,help`, `,skill name=my-sk
|
|
|
134
136
|
|
|
135
137
|
## Configuration
|
|
136
138
|
|
|
137
|
-
| Variable
|
|
138
|
-
|
|
139
|
-
| `BUB_MODEL`
|
|
140
|
-
| `BUB_API_KEY`
|
|
141
|
-
| `BUB_API_BASE`
|
|
142
|
-
| `BUB_API_FORMAT`
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
| `
|
|
139
|
+
| Variable | Default | Description |
|
|
140
|
+
|----------|---------|-------------|
|
|
141
|
+
| `BUB_MODEL` | `openrouter:qwen/qwen3-coder-next` | Model identifier |
|
|
142
|
+
| `BUB_API_KEY` | — | Provider key (optional with `bub login openai`) |
|
|
143
|
+
| `BUB_API_BASE` | — | Custom provider endpoint |
|
|
144
|
+
| `BUB_API_FORMAT` | `completion` | `completion`, `responses`, or `messages` |
|
|
145
|
+
| `BUB_CLIENT_ARGS` | — | JSON object forwarded to the underlying model client |
|
|
146
|
+
| `BUB_MAX_STEPS` | `50` | Max tool-use loop iterations |
|
|
147
|
+
| `BUB_MAX_TOKENS` | `1024` | Max tokens per model call |
|
|
148
|
+
| `BUB_MODEL_TIMEOUT_SECONDS` | — | Model call timeout (seconds) |
|
|
146
149
|
|
|
147
150
|
## Background
|
|
148
151
|
|
|
@@ -152,13 +155,13 @@ Read more: [Context from Tape](https://tape.systems) · [Socialized Evaluation a
|
|
|
152
155
|
|
|
153
156
|
## Docs
|
|
154
157
|
|
|
155
|
-
- [Architecture](
|
|
156
|
-
- [Features](
|
|
157
|
-
- [Channels](
|
|
158
|
-
- [Skills](
|
|
159
|
-
- [Extension Guide](
|
|
160
|
-
- [Deployment](
|
|
161
|
-
- [Posts](
|
|
158
|
+
- [Architecture](https://bub.build/architecture/) — lifecycle, hook precedence, error handling
|
|
159
|
+
- [Features](https://bub.build/features/) — what ships today and current boundaries
|
|
160
|
+
- [Channels](https://bub.build/channels/) — CLI, Telegram, and custom adapters
|
|
161
|
+
- [Skills](https://bub.build/skills/) — discovery and authoring
|
|
162
|
+
- [Extension Guide](https://bub.build/extension-guide/) — hooks, tools, plugin packaging
|
|
163
|
+
- [Deployment](https://bub.build/deployment/) — Docker, environment, upgrades
|
|
164
|
+
- [Posts](https://bub.build/posts/) — design notes
|
|
162
165
|
|
|
163
166
|
## Development
|
|
164
167
|
|
|
@@ -168,8 +171,8 @@ uv run mypy src
|
|
|
168
171
|
uv run pytest -q
|
|
169
172
|
```
|
|
170
173
|
|
|
171
|
-
See [CONTRIBUTING.md](
|
|
174
|
+
See [CONTRIBUTING.md](https://github.com/bubbuild/bub/blob/main/CONTRIBUTING.md).
|
|
172
175
|
|
|
173
176
|
## License
|
|
174
177
|
|
|
175
|
-
[Apache-2.0](
|
|
178
|
+
[Apache-2.0](https://github.com/bubbuild/bub/blob/main/LICENSE)
|
|
@@ -53,10 +53,10 @@ If `AGENTS.md` exists in the workspace, it is appended to the system prompt auto
|
|
|
53
53
|
|
|
54
54
|
Key source files:
|
|
55
55
|
|
|
56
|
-
- Turn orchestrator: [`src/bub/framework.py`](src/bub/framework.py)
|
|
57
|
-
- Hook contract: [`src/bub/hookspecs.py`](src/bub/hookspecs.py)
|
|
58
|
-
- Builtin hooks: [`src/bub/builtin/hook_impl.py`](src/bub/builtin/hook_impl.py)
|
|
59
|
-
- Skill discovery: [`src/bub/skills.py`](src/bub/skills.py)
|
|
56
|
+
- Turn orchestrator: [`src/bub/framework.py`](https://github.com/bubbuild/bub/blob/main/src/bub/framework.py)
|
|
57
|
+
- Hook contract: [`src/bub/hookspecs.py`](https://github.com/bubbuild/bub/blob/main/src/bub/hookspecs.py)
|
|
58
|
+
- Builtin hooks: [`src/bub/builtin/hook_impl.py`](https://github.com/bubbuild/bub/blob/main/src/bub/builtin/hook_impl.py)
|
|
59
|
+
- Skill discovery: [`src/bub/skills.py`](https://github.com/bubbuild/bub/blob/main/src/bub/skills.py)
|
|
60
60
|
|
|
61
61
|
## What Sets It Apart
|
|
62
62
|
|
|
@@ -103,15 +103,16 @@ Lines starting with `,` enter internal command mode (`,help`, `,skill name=my-sk
|
|
|
103
103
|
|
|
104
104
|
## Configuration
|
|
105
105
|
|
|
106
|
-
| Variable
|
|
107
|
-
|
|
108
|
-
| `BUB_MODEL`
|
|
109
|
-
| `BUB_API_KEY`
|
|
110
|
-
| `BUB_API_BASE`
|
|
111
|
-
| `BUB_API_FORMAT`
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
106
|
+
| Variable | Default | Description |
|
|
107
|
+
|----------|---------|-------------|
|
|
108
|
+
| `BUB_MODEL` | `openrouter:qwen/qwen3-coder-next` | Model identifier |
|
|
109
|
+
| `BUB_API_KEY` | — | Provider key (optional with `bub login openai`) |
|
|
110
|
+
| `BUB_API_BASE` | — | Custom provider endpoint |
|
|
111
|
+
| `BUB_API_FORMAT` | `completion` | `completion`, `responses`, or `messages` |
|
|
112
|
+
| `BUB_CLIENT_ARGS` | — | JSON object forwarded to the underlying model client |
|
|
113
|
+
| `BUB_MAX_STEPS` | `50` | Max tool-use loop iterations |
|
|
114
|
+
| `BUB_MAX_TOKENS` | `1024` | Max tokens per model call |
|
|
115
|
+
| `BUB_MODEL_TIMEOUT_SECONDS` | — | Model call timeout (seconds) |
|
|
115
116
|
|
|
116
117
|
## Background
|
|
117
118
|
|
|
@@ -121,13 +122,13 @@ Read more: [Context from Tape](https://tape.systems) · [Socialized Evaluation a
|
|
|
121
122
|
|
|
122
123
|
## Docs
|
|
123
124
|
|
|
124
|
-
- [Architecture](
|
|
125
|
-
- [Features](
|
|
126
|
-
- [Channels](
|
|
127
|
-
- [Skills](
|
|
128
|
-
- [Extension Guide](
|
|
129
|
-
- [Deployment](
|
|
130
|
-
- [Posts](
|
|
125
|
+
- [Architecture](https://bub.build/architecture/) — lifecycle, hook precedence, error handling
|
|
126
|
+
- [Features](https://bub.build/features/) — what ships today and current boundaries
|
|
127
|
+
- [Channels](https://bub.build/channels/) — CLI, Telegram, and custom adapters
|
|
128
|
+
- [Skills](https://bub.build/skills/) — discovery and authoring
|
|
129
|
+
- [Extension Guide](https://bub.build/extension-guide/) — hooks, tools, plugin packaging
|
|
130
|
+
- [Deployment](https://bub.build/deployment/) — Docker, environment, upgrades
|
|
131
|
+
- [Posts](https://bub.build/posts/) — design notes
|
|
131
132
|
|
|
132
133
|
## Development
|
|
133
134
|
|
|
@@ -137,8 +138,8 @@ uv run mypy src
|
|
|
137
138
|
uv run pytest -q
|
|
138
139
|
```
|
|
139
140
|
|
|
140
|
-
See [CONTRIBUTING.md](
|
|
141
|
+
See [CONTRIBUTING.md](https://github.com/bubbuild/bub/blob/main/CONTRIBUTING.md).
|
|
141
142
|
|
|
142
143
|
## License
|
|
143
144
|
|
|
144
|
-
[Apache-2.0](
|
|
145
|
+
[Apache-2.0](https://github.com/bubbuild/bub/blob/main/LICENSE)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "bub"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.3"
|
|
4
4
|
description = "A common shape for agents that live alongside people."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Chojan Shang", email = "psiace@apache.org" },
|
|
@@ -42,6 +42,11 @@ Documentation = "https://bub.build"
|
|
|
42
42
|
[project.scripts]
|
|
43
43
|
bub = "bub.__main__:app"
|
|
44
44
|
|
|
45
|
+
[project.optional-dependencies]
|
|
46
|
+
logfire = [
|
|
47
|
+
"logfire>=4.31.0",
|
|
48
|
+
]
|
|
49
|
+
|
|
45
50
|
[dependency-groups]
|
|
46
51
|
dev = [
|
|
47
52
|
"pytest>=7.2.0",
|
|
@@ -7,7 +7,21 @@ import typer
|
|
|
7
7
|
from bub.framework import BubFramework
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
def _instrument_bub() -> None:
|
|
11
|
+
try:
|
|
12
|
+
import logfire
|
|
13
|
+
|
|
14
|
+
logfire.configure()
|
|
15
|
+
except ImportError:
|
|
16
|
+
pass
|
|
17
|
+
else:
|
|
18
|
+
from loguru import logger
|
|
19
|
+
|
|
20
|
+
logger.configure(handlers=[logfire.loguru_handler()])
|
|
21
|
+
|
|
22
|
+
|
|
10
23
|
def create_cli_app() -> typer.Typer:
|
|
24
|
+
_instrument_bub()
|
|
11
25
|
framework = BubFramework()
|
|
12
26
|
framework.load_hooks()
|
|
13
27
|
app = framework.create_cli_app()
|
|
@@ -28,7 +28,6 @@ from bub.types import State
|
|
|
28
28
|
from bub.utils import workspace_from_state
|
|
29
29
|
|
|
30
30
|
CONTINUE_PROMPT = "Continue the task."
|
|
31
|
-
DEFAULT_BUB_HEADERS = {"HTTP-Referer": "https://bub.build/", "X-Title": "Bub"}
|
|
32
31
|
HINT_RE = re.compile(r"\$([A-Za-z0-9_.-]+)")
|
|
33
32
|
|
|
34
33
|
|
|
@@ -222,7 +221,6 @@ class Agent:
|
|
|
222
221
|
allowed_tools: Collection[str] | None = None,
|
|
223
222
|
allowed_skills: Collection[str] | None = None,
|
|
224
223
|
) -> ToolAutoResult:
|
|
225
|
-
extra_options = {"extra_headers": DEFAULT_BUB_HEADERS} if self.settings.model.startswith("openrouter:") else {}
|
|
226
224
|
prompt_text = prompt if isinstance(prompt, str) else _extract_text_from_parts(prompt)
|
|
227
225
|
if allowed_tools is not None:
|
|
228
226
|
allowed_tools = {name.casefold() for name in allowed_tools}
|
|
@@ -240,7 +238,6 @@ class Agent:
|
|
|
240
238
|
max_tokens=self.settings.max_tokens,
|
|
241
239
|
tools=model_tools(tools),
|
|
242
240
|
model=model,
|
|
243
|
-
**extra_options,
|
|
244
241
|
)
|
|
245
242
|
|
|
246
243
|
def _system_prompt(self, prompt: str, state: State, allowed_skills: set[str] | None = None) -> str:
|
|
@@ -284,6 +281,7 @@ def _build_llm(settings: AgentSettings, tape_store: AsyncTapeStore, tape_context
|
|
|
284
281
|
fallback_models=settings.fallback_models,
|
|
285
282
|
api_key_resolver=openai_codex_oauth_resolver(),
|
|
286
283
|
tape_store=tape_store,
|
|
284
|
+
client_args=settings.client_args,
|
|
287
285
|
api_format=settings.api_format,
|
|
288
286
|
context=tape_context,
|
|
289
287
|
verbose=settings.verbose,
|
|
@@ -117,7 +117,7 @@ class BuiltinImpl:
|
|
|
117
117
|
app.command("chat")(cli.chat)
|
|
118
118
|
app.add_typer(cli.login_app)
|
|
119
119
|
app.command("hooks", hidden=True)(cli.list_hooks)
|
|
120
|
-
app.command("
|
|
120
|
+
app.command("gateway")(cli.gateway)
|
|
121
121
|
|
|
122
122
|
def _read_agents_file(self, state: State) -> str:
|
|
123
123
|
workspace = state.get("_runtime_workspace", str(Path.cwd()))
|
|
@@ -5,7 +5,7 @@ import pathlib
|
|
|
5
5
|
import re
|
|
6
6
|
from collections.abc import Callable
|
|
7
7
|
from functools import lru_cache
|
|
8
|
-
from typing import Literal
|
|
8
|
+
from typing import Any, Literal
|
|
9
9
|
|
|
10
10
|
from pydantic import Field
|
|
11
11
|
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict, YamlConfigSettingsSource
|
|
@@ -45,6 +45,7 @@ class AgentSettings(BaseSettings):
|
|
|
45
45
|
max_steps: int = 50
|
|
46
46
|
max_tokens: int = DEFAULT_MAX_TOKENS
|
|
47
47
|
model_timeout_seconds: int | None = None
|
|
48
|
+
client_args: dict[str, Any] | None = None
|
|
48
49
|
verbose: int = Field(default=0, description="Verbosity level for logging. Higher means more verbose.", ge=0, le=2)
|
|
49
50
|
|
|
50
51
|
@classmethod
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import contextlib
|
|
5
|
+
import os
|
|
5
6
|
import uuid
|
|
6
7
|
from dataclasses import dataclass, field
|
|
7
8
|
|
|
@@ -38,6 +39,7 @@ class ShellManager:
|
|
|
38
39
|
cwd=cwd,
|
|
39
40
|
stdout=asyncio.subprocess.PIPE,
|
|
40
41
|
stderr=asyncio.subprocess.PIPE,
|
|
42
|
+
executable="/bin/bash" if os.name != "nt" else None,
|
|
41
43
|
)
|
|
42
44
|
shell = ManagedShell(shell_id=f"bash-{uuid.uuid4().hex[:8]}", cmd=cmd, cwd=cwd, process=process)
|
|
43
45
|
shell.read_tasks.extend([
|
|
@@ -37,7 +37,7 @@ class ForkTapeStore:
|
|
|
37
37
|
|
|
38
38
|
@property
|
|
39
39
|
def _current(self) -> TapeStore:
|
|
40
|
-
return current_store.get(
|
|
40
|
+
return current_store.get(_empty_store)
|
|
41
41
|
|
|
42
42
|
@property
|
|
43
43
|
def _fork_tape(self) -> str | None:
|
|
@@ -52,7 +52,7 @@ class ForkTapeStore:
|
|
|
52
52
|
|
|
53
53
|
async def reset(self, tape: str) -> None:
|
|
54
54
|
self._current.reset(tape)
|
|
55
|
-
if self._current is
|
|
55
|
+
if self._current is _empty_store or self._fork_tape != tape:
|
|
56
56
|
await self._parent.reset(tape)
|
|
57
57
|
return
|
|
58
58
|
current_tape_was_reset.set(True)
|
|
@@ -138,7 +138,7 @@ class EmptyTapeStore:
|
|
|
138
138
|
pass
|
|
139
139
|
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
_empty_store = EmptyTapeStore()
|
|
142
142
|
|
|
143
143
|
|
|
144
144
|
class FileTapeStore(InMemoryQueryMixin):
|
|
@@ -206,7 +206,9 @@ async def tape_search(param: SearchInput, *, context: ToolContext) -> str:
|
|
|
206
206
|
if "[tape.search]" in entry_str:
|
|
207
207
|
continue
|
|
208
208
|
lines.append(entry_str)
|
|
209
|
-
return f"[tape.search]: {len(entries)}
|
|
209
|
+
return f"[tape.search]: {len(lines)} matches ({len(entries) - len(lines)} filtered)" + "".join(
|
|
210
|
+
f"\n{line}" for line in lines
|
|
211
|
+
)
|
|
210
212
|
|
|
211
213
|
|
|
212
214
|
@tool(context=True, name="tape.reset")
|
|
@@ -26,7 +26,12 @@ def test_build_llm_passes_codex_resolver_to_republic(monkeypatch) -> None:
|
|
|
26
26
|
monkeypatch.setattr(agent_module, "LLM", FakeLLM)
|
|
27
27
|
monkeypatch.setattr(openai_codex, "openai_codex_oauth_resolver", lambda: resolver)
|
|
28
28
|
|
|
29
|
-
settings = AgentSettings(
|
|
29
|
+
settings = AgentSettings(
|
|
30
|
+
model="openai:gpt-5-codex",
|
|
31
|
+
api_key=None,
|
|
32
|
+
api_base=None,
|
|
33
|
+
client_args={"extra_headers": {"HTTP-Referer": "https://openclaw.ai", "X-Title": "OpenClaw"}},
|
|
34
|
+
)
|
|
30
35
|
tape_store = object()
|
|
31
36
|
|
|
32
37
|
agent_module._build_llm(settings, tape_store, "ctx")
|
|
@@ -34,6 +39,9 @@ def test_build_llm_passes_codex_resolver_to_republic(monkeypatch) -> None:
|
|
|
34
39
|
assert captured["args"] == ("openai:gpt-5-codex",)
|
|
35
40
|
assert captured["kwargs"]["api_key"] is None
|
|
36
41
|
assert captured["kwargs"]["api_base"] is None
|
|
42
|
+
assert captured["kwargs"]["client_args"] == {
|
|
43
|
+
"extra_headers": {"HTTP-Referer": "https://openclaw.ai", "X-Title": "OpenClaw"},
|
|
44
|
+
}
|
|
37
45
|
assert captured["kwargs"]["api_key_resolver"] is resolver
|
|
38
46
|
assert captured["kwargs"]["tape_store"] is tape_store
|
|
39
47
|
assert captured["kwargs"]["context"] == "ctx"
|
|
@@ -94,19 +94,19 @@ def test_get_system_prompt_uses_priority_order_and_skips_empty_results() -> None
|
|
|
94
94
|
assert prompt == "low\n\nhigh"
|
|
95
95
|
|
|
96
96
|
|
|
97
|
-
def
|
|
97
|
+
def test_builtin_cli_exposes_login_and_gateway_command() -> None:
|
|
98
98
|
framework = BubFramework()
|
|
99
99
|
framework.load_hooks()
|
|
100
100
|
app = framework.create_cli_app()
|
|
101
101
|
runner = CliRunner()
|
|
102
102
|
|
|
103
103
|
help_result = runner.invoke(app, ["--help"])
|
|
104
|
-
|
|
104
|
+
gateway_result = runner.invoke(app, ["gateway", "--help"])
|
|
105
105
|
|
|
106
106
|
assert help_result.exit_code == 0
|
|
107
107
|
assert "login" in help_result.stdout
|
|
108
108
|
assert "gateway" in help_result.stdout
|
|
109
109
|
assert "│ message" not in help_result.stdout
|
|
110
|
-
assert
|
|
111
|
-
assert "bub
|
|
112
|
-
assert "Start message listeners" in
|
|
110
|
+
assert gateway_result.exit_code == 0
|
|
111
|
+
assert "bub gateway" in gateway_result.stdout
|
|
112
|
+
assert "Start message listeners" in gateway_result.stdout
|
|
@@ -37,11 +37,12 @@ def test_settings_per_provider_keys() -> None:
|
|
|
37
37
|
assert settings.api_base["openai"] == "https://api.openai.com"
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
def
|
|
40
|
+
def test_settings_no_keys_return_none() -> None:
|
|
41
41
|
settings = _settings_with_env({})
|
|
42
42
|
|
|
43
43
|
assert settings.api_key is None
|
|
44
44
|
assert settings.api_base is None
|
|
45
|
+
assert settings.client_args is None
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
def test_settings_provider_names_are_lowercased() -> None:
|
|
@@ -74,6 +75,10 @@ api_key:
|
|
|
74
75
|
openai: sk-yaml
|
|
75
76
|
api_base:
|
|
76
77
|
openai: https://api.openai.com
|
|
78
|
+
client_args:
|
|
79
|
+
extra_headers:
|
|
80
|
+
HTTP-Referer: https://openclaw.ai
|
|
81
|
+
X-Title: OpenClaw
|
|
77
82
|
""".strip(),
|
|
78
83
|
)
|
|
79
84
|
|
|
@@ -85,6 +90,9 @@ api_base:
|
|
|
85
90
|
assert settings.max_steps == 77
|
|
86
91
|
assert settings.api_key == {"openai": "sk-yaml"}
|
|
87
92
|
assert settings.api_base == {"openai": "https://api.openai.com"}
|
|
93
|
+
assert settings.client_args == {
|
|
94
|
+
"extra_headers": {"HTTP-Referer": "https://openclaw.ai", "X-Title": "OpenClaw"},
|
|
95
|
+
}
|
|
88
96
|
|
|
89
97
|
|
|
90
98
|
def test_env_settings_override_yaml(tmp_path: Path) -> None:
|
|
@@ -94,6 +102,10 @@ def test_env_settings_override_yaml(tmp_path: Path) -> None:
|
|
|
94
102
|
model: openai:gpt-5
|
|
95
103
|
api_key: sk-yaml
|
|
96
104
|
max_steps: 77
|
|
105
|
+
client_args:
|
|
106
|
+
extra_headers:
|
|
107
|
+
HTTP-Referer: https://yaml.example
|
|
108
|
+
X-Title: YAML App
|
|
97
109
|
""".strip(),
|
|
98
110
|
)
|
|
99
111
|
|
|
@@ -103,6 +115,7 @@ max_steps: 77
|
|
|
103
115
|
"BUB_HOME": str(tmp_path),
|
|
104
116
|
"BUB_MODEL": "anthropic:claude-3-7-sonnet",
|
|
105
117
|
"BUB_API_KEY": "sk-env",
|
|
118
|
+
"BUB_CLIENT_ARGS": '{"extra_headers":{"HTTP-Referer":"https://env.example","X-Title":"Env App"}}',
|
|
106
119
|
"BUB_MAX_STEPS": "12",
|
|
107
120
|
},
|
|
108
121
|
clear=True,
|
|
@@ -112,6 +125,15 @@ max_steps: 77
|
|
|
112
125
|
assert settings.model == "anthropic:claude-3-7-sonnet"
|
|
113
126
|
assert settings.api_key == "sk-env"
|
|
114
127
|
assert settings.max_steps == 12
|
|
128
|
+
assert settings.client_args == {
|
|
129
|
+
"extra_headers": {"HTTP-Referer": "https://env.example", "X-Title": "Env App"},
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_settings_client_args_can_be_disabled() -> None:
|
|
134
|
+
settings = _settings_with_env({"BUB_CLIENT_ARGS": "null"})
|
|
135
|
+
|
|
136
|
+
assert settings.client_args is None
|
|
115
137
|
|
|
116
138
|
|
|
117
139
|
def test_load_settings_reads_yaml_from_bub_home(tmp_path: Path) -> None:
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from republic import ToolContext
|
|
7
|
+
|
|
8
|
+
import bub.builtin.tools as builtin_tools
|
|
9
|
+
from bub.builtin.tools import tape_search
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class _FakeEntry:
|
|
14
|
+
date: str
|
|
15
|
+
payload: object
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _FakeTapes:
|
|
19
|
+
def __init__(self, entries: list[_FakeEntry]) -> None:
|
|
20
|
+
self._entries = entries
|
|
21
|
+
self._store = object()
|
|
22
|
+
|
|
23
|
+
async def search(self, _query: object) -> list[_FakeEntry]:
|
|
24
|
+
return list(self._entries)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _FakeAgent:
|
|
28
|
+
def __init__(self, entries: list[_FakeEntry]) -> None:
|
|
29
|
+
self.tapes = _FakeTapes(entries)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.mark.asyncio
|
|
33
|
+
async def test_tape_search_reports_shown_matches_and_filtered_count(monkeypatch) -> None:
|
|
34
|
+
entries = [
|
|
35
|
+
_FakeEntry(date="2026-01-01T00:00:00Z", payload={"content": "ok"}),
|
|
36
|
+
_FakeEntry(date="2026-01-01T00:00:01Z", payload={"content": "[tape.search]: 1 matches"}),
|
|
37
|
+
]
|
|
38
|
+
monkeypatch.setattr(builtin_tools, "_get_agent", lambda _context: _FakeAgent(entries))
|
|
39
|
+
|
|
40
|
+
output = await tape_search.run(query="x", context=ToolContext(tape="tape", run_id="run", state={}))
|
|
41
|
+
|
|
42
|
+
assert output.splitlines()[0] == "[tape.search]: 1 matches (1 filtered)"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.mark.asyncio
|
|
46
|
+
async def test_tape_search_reports_zero_filtered_explicitly(monkeypatch) -> None:
|
|
47
|
+
entries = [
|
|
48
|
+
_FakeEntry(date="2026-01-01T00:00:00Z", payload={"content": "a"}),
|
|
49
|
+
_FakeEntry(date="2026-01-01T00:00:01Z", payload={"content": "b"}),
|
|
50
|
+
]
|
|
51
|
+
monkeypatch.setattr(builtin_tools, "_get_agent", lambda _context: _FakeAgent(entries))
|
|
52
|
+
|
|
53
|
+
output = await tape_search.run(query="x", context=ToolContext(tape="tape", run_id="run", state={}))
|
|
54
|
+
|
|
55
|
+
assert output.splitlines()[0] == "[tape.search]: 2 matches (0 filtered)"
|
{bub-0.3.2 → bub-0.3.3}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|