Skip to content

Commit 3785974

Browse files
Enterprise Cookbook preview: OpenHands/enterprise-cookbook#13 (do not merge)
Preview of the Enterprise Cookbook pages produced by OpenHands/enterprise-cookbook#13 (Publish conversation examples to the docs Cookbook tab), rendered from OpenHands/enterprise-cookbook@b73c7c4. **Do not merge.** This draft exists only to get a Mintlify preview. It is updated on every push to the source PR and closed when that PR closes. Merged changes reach the docs through a separate `cookbook-sync` PR. _Opened automatically by the docs-preview workflow in OpenHands/enterprise-cookbook._
1 parent 605ea3e commit 3785974

9 files changed

Lines changed: 1220 additions & 16 deletions

‎cookbook/command-blacklist.mdx‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ description: Block known-dangerous shell commands with a PreToolUse hook bundled
44
icon: shield-halved
55
---
66

7-
{/* GENERATED from OpenHands/enterprise-cookbook@main (command-blacklist/README.md). Edit the source, not this file. */}
7+
{/* GENERATED from OpenHands/enterprise-cookbook@b73c7c4dec2be0f1ed2258deebc2e49013064c38 (command-blacklist/README.md). Edit the source, not this file. */}
88

9-
<Card title="View source on GitHub" icon="github" href="https://github.com/OpenHands/enterprise-cookbook/tree/main/command-blacklist" horizontal />
9+
<Card title="View source on GitHub" icon="github" href="https://github.com/OpenHands/enterprise-cookbook/tree/b73c7c4dec2be0f1ed2258deebc2e49013064c38/command-blacklist" horizontal />
1010

1111
A self-contained example showing how to use **PreToolUse hooks** in a plugin to **blacklist dangerous shell commands**. When the agent tries to execute a risky command, the hook blocks it with helpful (and slightly snarky) feedback.
1212

1313
This example demonstrates the **blacklist approach**: block known dangerous patterns while allowing everything else to proceed normally.
1414

1515
## What's in the Box
1616

17-
The [`safety-guardian/`](https://github.com/OpenHands/enterprise-cookbook/tree/main/command-blacklist/safety-guardian) plugin bundles:
17+
The [`safety-guardian/`](https://github.com/OpenHands/enterprise-cookbook/tree/b73c7c4dec2be0f1ed2258deebc2e49013064c38/command-blacklist/safety-guardian) plugin bundles:
1818

1919
- **Hooks** (`hooks/hooks.json`) - PreToolUse hook that intercepts terminal commands
2020
- **Skill** (`skills/safety-guardian/SKILL.md`) - Documentation about what's protected
@@ -60,7 +60,7 @@ All other commands work normally - only these specific dangerous patterns are bl
6060

6161
<Tabs>
6262
<Tab title="Load via API">
63-
Use the companion [`load-plugin`](https://github.com/OpenHands/enterprise-cookbook/tree/main/load-plugin) example:
63+
Use the companion [`load-plugin`](https://github.com/OpenHands/enterprise-cookbook/tree/b73c7c4dec2be0f1ed2258deebc2e49013064c38/load-plugin) example:
6464

6565
```bash
6666
cd ../load-plugin
@@ -96,7 +96,7 @@ All other commands work normally - only these specific dangerous patterns are bl
9696

9797
## The Hook
9898

99-
The magic happens in [`hooks/hooks.json`](https://github.com/OpenHands/enterprise-cookbook/blob/main/command-blacklist/safety-guardian/hooks/hooks.json):
99+
The magic happens in [`hooks/hooks.json`](https://github.com/OpenHands/enterprise-cookbook/blob/b73c7c4dec2be0f1ed2258deebc2e49013064c38/command-blacklist/safety-guardian/hooks/hooks.json):
100100

101101
```json safety-guardian/hooks/hooks.json
102102
{
@@ -156,7 +156,7 @@ This example uses a **blacklist** approach:
156156
- ❌ **Con:** Can't catch every dangerous pattern
157157
- ❌ **Con:** Clever variations might slip through
158158

159-
For high-security scenarios, see the companion [`command-whitelist`](https://github.com/OpenHands/enterprise-cookbook/tree/main/command-whitelist) example that shows the **whitelist** approach (only allow explicitly approved commands).
159+
For high-security scenarios, see the companion [`command-whitelist`](https://github.com/OpenHands/enterprise-cookbook/tree/b73c7c4dec2be0f1ed2258deebc2e49013064c38/command-whitelist) example that shows the **whitelist** approach (only allow explicitly approved commands).
160160

161161
## Hook Types
162162

@@ -201,15 +201,15 @@ This follows the **Claude Code plugin format**, compatible with:
201201
How plugins work
202202
</Card>
203203

204-
<Card title="load-plugin" href="https://github.com/OpenHands/enterprise-cookbook/tree/main/load-plugin" icon="arrow-up-right-from-square">
204+
<Card title="load-plugin" href="https://github.com/OpenHands/enterprise-cookbook/tree/b73c7c4dec2be0f1ed2258deebc2e49013064c38/load-plugin" icon="arrow-up-right-from-square">
205205
Programmatic plugin loading
206206
</Card>
207207

208-
<Card title="launch-plugin-badge" href="https://github.com/OpenHands/enterprise-cookbook/tree/main/launch-plugin-badge" icon="arrow-up-right-from-square">
208+
<Card title="launch-plugin-badge" href="https://github.com/OpenHands/enterprise-cookbook/tree/b73c7c4dec2be0f1ed2258deebc2e49013064c38/launch-plugin-badge" icon="arrow-up-right-from-square">
209209
No-code plugin launcher
210210
</Card>
211211

212-
<Card title="command-whitelist" href="https://github.com/OpenHands/enterprise-cookbook/tree/main/command-whitelist" icon="arrow-up-right-from-square">
212+
<Card title="command-whitelist" href="https://github.com/OpenHands/enterprise-cookbook/tree/b73c7c4dec2be0f1ed2258deebc2e49013064c38/command-whitelist" icon="arrow-up-right-from-square">
213213
Whitelist approach (opposite strategy)
214214
</Card>
215215
</CardGroup>

‎cookbook/conversation-metrics.mdx‎

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
---
2+
title: Conversation Metrics
3+
description: Retrieve cost and token usage metrics for conversations via CLI tool.
4+
icon: chart-line
5+
---
6+
7+
{/* GENERATED from OpenHands/enterprise-cookbook@b73c7c4dec2be0f1ed2258deebc2e49013064c38 (conversation-metrics/README.md). Edit the source, not this file. */}
8+
9+
<Card title="View source on GitHub" icon="github" href="https://github.com/OpenHands/enterprise-cookbook/tree/b73c7c4dec2be0f1ed2258deebc2e49013064c38/conversation-metrics" horizontal />
10+
11+
CLI tool to retrieve cost and token usage metrics for OpenHands conversations.
12+
13+
Supports both **V0** and **V1** APIs, automatically selecting the appropriate one based on the conversation version.
14+
15+
## Features
16+
17+
- **Auto-detects** conversation version (V0 vs V1) and uses the appropriate API
18+
- **Graceful fallback** chain for metric retrieval
19+
- **Displays** cost (USD), token counts, cache stats, and context window
20+
- **JSON output** for programmatic use
21+
- **API call logging** for debugging and development
22+
- **Fixture-based testing** - high coverage via recorded API responses
23+
- **Zero dependencies** - uses only Python standard library
24+
25+
## Installation
26+
27+
No installation required - just make the script executable:
28+
29+
```bash
30+
chmod +x oh-metrics
31+
```
32+
33+
Requires Python 3.10+.
34+
35+
## Usage
36+
37+
### Set your API key
38+
39+
```bash
40+
export OH_API_KEY="your-api-key"
41+
```
42+
43+
### Get metrics for a conversation
44+
45+
```bash
46+
./oh-metrics <conversation_id>
47+
```
48+
49+
**Example output (V0 conversation):**
50+
51+
```text
52+
────────────────────────────────────────────────────────────
53+
Conversation: 7f3d57c4e5b2434d9ca78e5e27311137
54+
Title: Hello and a Programming Joke
55+
API Version: V0 via V0 (events)
56+
────────────────────────────────────────────────────────────
57+
💰 Total Cost: $0.083175 USD
58+
59+
📊 Token Usage:
60+
Prompt tokens: 12,865
61+
Completion tokens: 117
62+
Total tokens: 12,982
63+
64+
🗄️ Cache:
65+
Cache read: 0
66+
Cache write: 12,740
67+
68+
📐 Context window: 200,000
69+
────────────────────────────────────────────────────────────
70+
```
71+
72+
**Example output (V1 conversation):**
73+
74+
```text
75+
────────────────────────────────────────────────────────────
76+
Conversation: 72d40619b8534f9b9de6c3f17a71072d
77+
Title: 📝 V0 API for Conversation Costs & Tokens
78+
API Version: V1 via V1 (events)
79+
────────────────────────────────────────────────────────────
80+
💰 Total Cost: $13.576675 USD
81+
82+
📊 Token Usage:
83+
Prompt tokens: 17,577,146
84+
Completion tokens: 63,985
85+
Total tokens: 17,641,131
86+
87+
🗄️ Cache:
88+
Cache read: 17,022,477
89+
Cache write: 553,973
90+
91+
🧠 Reasoning tokens: 1,169
92+
93+
📐 Context window: 200,000
94+
────────────────────────────────────────────────────────────
95+
```
96+
97+
### JSON output
98+
99+
```bash
100+
./oh-metrics <conversation_id> --json
101+
```
102+
103+
```json
104+
{
105+
"conversation_id": "7f3d57c4e5b2434d9ca78e5e27311137",
106+
"title": "Hello and a Programming Joke",
107+
"api_version": "V0",
108+
"api_used": "V0 (events)",
109+
"metrics": {
110+
"accumulated_cost": 0.083175,
111+
"accumulated_token_usage": {
112+
"prompt_tokens": 12865,
113+
"completion_tokens": 117,
114+
"cache_read_tokens": 0,
115+
"cache_write_tokens": 12740,
116+
"context_window": 200000
117+
}
118+
}
119+
}
120+
```
121+
122+
### Options
123+
124+
| Option | Short | Description |
125+
| ----------------- | ----- | ------------------------------------------------- |
126+
| `--api-key KEY` | `-k` | API key (defaults to `OH_API_KEY` env var) |
127+
| `--base-url URL` | `-u` | Base URL (default: `https://app.all-hands.dev`) |
128+
| `--json` | `-j` | Output in JSON format |
129+
| `--log-api-calls` | `-l` | Log all API requests/responses to `.oh/api-logs/` |
130+
| `--help` | `-h` | Show help message |
131+
132+
### Logging API Calls
133+
134+
For debugging or development, you can log all API requests and responses:
135+
136+
```bash
137+
./oh-metrics <conversation_id> --log-api-calls
138+
```
139+
140+
This creates timestamped files in `.oh/api-logs/YYYYMMDD-HHMMSS/`:
141+
142+
```text
143+
.oh/api-logs/20260227-145230/
144+
├── 0001-request.json
145+
├── 0001-response.json
146+
├── 0002-request.json
147+
└── 0002-response.json
148+
```
149+
150+
Each request/response pair is numbered sequentially. The Authorization header is redacted from logged requests.
151+
152+
## API Endpoints Used
153+
154+
### For V1 Conversations
155+
156+
| Endpoint | Purpose |
157+
| --------------------------------------------- | -------------------------------------------------------- |
158+
| `GET /api/conversations/{id}` | Get conversation info (to determine version) |
159+
| `GET /api/v1/app-conversations?ids={id}` | Get conversation with metrics |
160+
| `GET /api/v1/conversation/{id}/events/search` | Fallback: find metrics in `ConversationStateUpdateEvent` |
161+
162+
### For V0 Conversations
163+
164+
| Endpoint | Purpose |
165+
| ---------------------------------------- | ----------------------------- |
166+
| `GET /api/conversations/{id}` | Get conversation info |
167+
| `GET /api/conversations/{id}/events` | Get events with `llm_metrics` |
168+
| `GET /api/conversations/{id}/trajectory` | Fallback for metrics |
169+
170+
## How Metrics Are Retrieved
171+
172+
The tool uses a fallback chain to find metrics:
173+
174+
1. **Check conversation version** via `/api/conversations/{id}`
175+
2. **For V1 conversations**:
176+
- First try `/api/v1/app-conversations?ids={id}` which includes a `metrics` object
177+
- If metrics are all zeros, fall back to `/api/v1/conversation/{id}/events/search` and extract metrics from `ConversationStateUpdateEvent` at `value.stats.usage_to_metrics.agent`
178+
3. **For V0 conversations** (or if V1 fails): Use `/api/conversations/{id}/events` and find the latest event with `llm_metrics`
179+
4. **Last resort**: Use `/api/conversations/{id}/trajectory` and scan for `llm_metrics`
180+
181+
> **Note:** Some V1 conversations have metrics stored only in events (not in the app-conversations response). The fallback chain ensures these are still retrieved correctly.
182+
183+
## Metrics Explained
184+
185+
| Field | Description |
186+
| -------------------- | ----------------------------------------- |
187+
| `accumulated_cost` | Total cost in USD |
188+
| `prompt_tokens` | Tokens sent to the LLM |
189+
| `completion_tokens` | Tokens generated by the LLM |
190+
| `cache_read_tokens` | Tokens read from prompt cache |
191+
| `cache_write_tokens` | Tokens written to prompt cache |
192+
| `reasoning_tokens` | Tokens used for reasoning (if applicable) |
193+
| `context_window` | Model's context window size |
194+
195+
## Architecture
196+
197+
The library is organized into separate modules:
198+
199+
```text
200+
oh_api/
201+
├── __init__.py # Public API exports
202+
├── client.py # Base HTTP client with logging and fixture support
203+
├── v0.py # V0 API driver
204+
├── v1.py # V1 API driver
205+
└── metrics.py # High-level metrics retrieval with fallback chain
206+
```
207+
208+
### Using the Library Programmatically
209+
210+
```python
211+
from oh_api import APIClient, get_conversation_metrics
212+
213+
# Create a client
214+
client = APIClient(
215+
base_url="https://app.all-hands.dev",
216+
api_key="your-api-key",
217+
log_api_calls=True, # Optional: enable logging
218+
)
219+
220+
# Get metrics
221+
metrics = get_conversation_metrics(client, "conversation-id")
222+
if metrics:
223+
print(f"Cost: ${metrics.accumulated_cost:.6f}")
224+
print(f"Tokens: {metrics.total_tokens:,}")
225+
```
226+
227+
### Using the V0/V1 Drivers Directly
228+
229+
```python
230+
from oh_api import APIClient
231+
from oh_api.v0 import V0Driver
232+
from oh_api.v1 import V1Driver
233+
234+
client = APIClient(base_url="...", api_key="...")
235+
236+
# V0 API
237+
v0 = V0Driver(client)
238+
conv = v0.get_conversation("conv-id")
239+
events = v0.get_events("conv-id", limit=50)
240+
241+
# V1 API
242+
v1 = V1Driver(client)
243+
conv = v1.get_conversation("conv-id")
244+
if conv and conv.metrics:
245+
print(f"Cost: ${conv.metrics.accumulated_cost}")
246+
```
247+
248+
## Testing
249+
250+
The test suite uses recorded API fixtures to achieve high coverage without making real API calls:
251+
252+
```bash
253+
# Run tests
254+
uv run pytest tests/ -v
255+
256+
# Run with coverage
257+
uv run pytest tests/ --cov --cov-report=term-missing
258+
```
259+
260+
Current coverage: **89%** of library code.
261+
262+
### Creating New Fixtures
263+
264+
1. Run the CLI with `--log-api-calls` to capture real API responses
265+
2. Copy relevant response files to `tests/fixtures/`
266+
3. Rename following the pattern: `GET__api_path_q_param=value.json`
267+
268+
## License
269+
270+
MIT License - see [LICENSE](https://github.com/OpenHands/enterprise-cookbook/blob/b73c7c4dec2be0f1ed2258deebc2e49013064c38/LICENSE) for details.

‎cookbook/conversation-tags.mdx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
---
2-
title: Conversation tags
2+
title: Conversation Tags
33
description: Attach key-value metadata to a conversation with tags and read it back from AppConversation.tags.
44
icon: tags
55
---
66

7-
{/* GENERATED from OpenHands/enterprise-cookbook@main (conversation-tags/README.md). Edit the source, not this file. */}
7+
{/* GENERATED from OpenHands/enterprise-cookbook@b73c7c4dec2be0f1ed2258deebc2e49013064c38 (conversation-tags/README.md). Edit the source, not this file. */}
88

9-
<Card title="View source on GitHub" icon="github" href="https://github.com/OpenHands/enterprise-cookbook/tree/main/conversation-tags" horizontal />
9+
<Card title="View source on GitHub" icon="github" href="https://github.com/OpenHands/enterprise-cookbook/tree/b73c7c4dec2be0f1ed2258deebc2e49013064c38/conversation-tags" horizontal />
1010

1111
Stash your own key-value metadata on an OpenHands conversation — for example an
1212
external `environment_url` or `environment_conversation_id` — and read it back
@@ -49,7 +49,7 @@ and then *polls* the Cloud read instead of reading once.
4949
> the agent server is the authoritative place to write them, and the Cloud
5050
> reflects the result. The agent `POST /api/conversations` also accepts `tags`
5151
> at creation time if you provision the sandbox yourself (see
52-
> [`clone-and-attach`](https://github.com/OpenHands/enterprise-cookbook/tree/main/clone-and-attach)).
52+
> [`clone-and-attach`](https://github.com/OpenHands/enterprise-cookbook/tree/b73c7c4dec2be0f1ed2258deebc2e49013064c38/clone-and-attach)).
5353
5454
## Tag rules
5555

0 commit comments

Comments
 (0)