probiai 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.
- probiai-0.1.0/LICENSE +21 -0
- probiai-0.1.0/PKG-INFO +69 -0
- probiai-0.1.0/README.md +58 -0
- probiai-0.1.0/probiai.py +117 -0
- probiai-0.1.0/pyproject.toml +19 -0
probiai-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ProbiAI
|
|
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.
|
probiai-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: probiai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Sends ProbiAI a copy of each action your agent takes. Report-only, no dependencies.
|
|
5
|
+
Project-URL: Homepage, https://probiai-personalagent.vercel.app
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: agents,claude-agent-sdk,openai-agents,probiai,telemetry
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# probiai
|
|
13
|
+
|
|
14
|
+
Sends ProbiAI a copy of each action your agent takes, so your ProbiAI page shows what it did. For agents you build
|
|
15
|
+
with the Claude Agent SDK (Python or TypeScript) or the OpenAI Agents SDK (Python).
|
|
16
|
+
|
|
17
|
+
- Report-only: it never blocks, delays or changes your agent. If ProbiAI can't be reached, the copy is dropped.
|
|
18
|
+
- No dependencies.
|
|
19
|
+
- Contents and secrets in tool arguments (message bodies, file contents, passwords, card numbers, API keys) are
|
|
20
|
+
dropped before anything leaves your machine.
|
|
21
|
+
|
|
22
|
+
## Set up
|
|
23
|
+
|
|
24
|
+
1. On your ProbiAI page, choose your framework. It shows your key.
|
|
25
|
+
2. Put the key in your agent's environment, not in your code:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
export PROBIAI_KEY=pk....
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
3. Pass the hooks to your agent (merge them with any hooks of your own).
|
|
32
|
+
|
|
33
|
+
### Python
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install probiai
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import probiai
|
|
41
|
+
|
|
42
|
+
# Claude Agent SDK
|
|
43
|
+
options = ClaudeAgentOptions(hooks=probiai.claude_hooks())
|
|
44
|
+
|
|
45
|
+
# OpenAI Agents SDK
|
|
46
|
+
result = await Runner.run(agent, prompt, hooks=probiai.OpenAIHooks())
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### TypeScript (Claude Agent SDK)
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install probiai
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { claudeHooks } from "probiai";
|
|
57
|
+
|
|
58
|
+
const options = { hooks: claudeHooks() };
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## What is sent
|
|
62
|
+
|
|
63
|
+
For each tool call: the tool name, its arguments without contents or secrets, whether it worked, the first 500
|
|
64
|
+
characters of the result, and the model when known. Sub-agents and hand-offs are sent by name. Copies go to
|
|
65
|
+
`PROBIAI_URL` (default https://probiai-personalagent.vercel.app) with your key. Without a key, nothing is sent.
|
|
66
|
+
|
|
67
|
+
Using OpenClaw or Pi? Your ProbiAI page has a one-line install with your key built in.
|
|
68
|
+
|
|
69
|
+
ProbiAI is a pilot: it is being tested and is not yet an insurance product.
|
probiai-0.1.0/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# probiai
|
|
2
|
+
|
|
3
|
+
Sends ProbiAI a copy of each action your agent takes, so your ProbiAI page shows what it did. For agents you build
|
|
4
|
+
with the Claude Agent SDK (Python or TypeScript) or the OpenAI Agents SDK (Python).
|
|
5
|
+
|
|
6
|
+
- Report-only: it never blocks, delays or changes your agent. If ProbiAI can't be reached, the copy is dropped.
|
|
7
|
+
- No dependencies.
|
|
8
|
+
- Contents and secrets in tool arguments (message bodies, file contents, passwords, card numbers, API keys) are
|
|
9
|
+
dropped before anything leaves your machine.
|
|
10
|
+
|
|
11
|
+
## Set up
|
|
12
|
+
|
|
13
|
+
1. On your ProbiAI page, choose your framework. It shows your key.
|
|
14
|
+
2. Put the key in your agent's environment, not in your code:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
export PROBIAI_KEY=pk....
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
3. Pass the hooks to your agent (merge them with any hooks of your own).
|
|
21
|
+
|
|
22
|
+
### Python
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install probiai
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import probiai
|
|
30
|
+
|
|
31
|
+
# Claude Agent SDK
|
|
32
|
+
options = ClaudeAgentOptions(hooks=probiai.claude_hooks())
|
|
33
|
+
|
|
34
|
+
# OpenAI Agents SDK
|
|
35
|
+
result = await Runner.run(agent, prompt, hooks=probiai.OpenAIHooks())
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### TypeScript (Claude Agent SDK)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install probiai
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { claudeHooks } from "probiai";
|
|
46
|
+
|
|
47
|
+
const options = { hooks: claudeHooks() };
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## What is sent
|
|
51
|
+
|
|
52
|
+
For each tool call: the tool name, its arguments without contents or secrets, whether it worked, the first 500
|
|
53
|
+
characters of the result, and the model when known. Sub-agents and hand-offs are sent by name. Copies go to
|
|
54
|
+
`PROBIAI_URL` (default https://probiai-personalagent.vercel.app) with your key. Without a key, nothing is sent.
|
|
55
|
+
|
|
56
|
+
Using OpenClaw or Pi? Your ProbiAI page has a one-line install with your key built in.
|
|
57
|
+
|
|
58
|
+
ProbiAI is a pilot: it is being tested and is not yet an insurance product.
|
probiai-0.1.0/probiai.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""ProbiAI add-on for agents you build in Python: a copy of each action your agent takes goes to ProbiAI automatically.
|
|
2
|
+
|
|
3
|
+
Claude Agent SDK: options = ClaudeAgentOptions(hooks=probiai.claude_hooks()) # merge with hooks of your own
|
|
4
|
+
OpenAI Agents SDK: result = await Runner.run(agent, prompt, hooks=probiai.OpenAIHooks())
|
|
5
|
+
|
|
6
|
+
Set PROBIAI_KEY (from your ProbiAI page). Standard library only. Report-only: it never blocks, delays or changes your
|
|
7
|
+
agent. Copies go out on a background thread (your program waits at most 5 seconds for the last ones when it exits);
|
|
8
|
+
failures are ignored. Contents and secrets in tool arguments are dropped before anything leaves your machine.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import threading
|
|
16
|
+
import urllib.request
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
|
|
19
|
+
URL = os.environ.get("PROBIAI_URL", "https://probiai-personalagent.vercel.app").rstrip("/")
|
|
20
|
+
DROP = re.compile(r"^(body|text|content|contents|message|html|markdown|attachments?|file_?content|data|base64|password|"
|
|
21
|
+
r"passcode|pin|otp|token|secret|api_?key|card(_?number)?|cvv|cvc|ssn)$", re.I)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def facts(value, depth: int = 0):
|
|
25
|
+
"""The arguments with contents and secrets removed, nested at most four levels, long strings cut."""
|
|
26
|
+
if depth > 3:
|
|
27
|
+
return "…"
|
|
28
|
+
if isinstance(value, (list, tuple)):
|
|
29
|
+
return [facts(v, depth + 1) for v in list(value)[:50]]
|
|
30
|
+
if isinstance(value, dict):
|
|
31
|
+
return {k: facts(v, depth + 1) for k, v in list(value.items())[:40] if not DROP.match(str(k))}
|
|
32
|
+
return value[:200] if isinstance(value, str) else value
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def short(result) -> str | None:
|
|
36
|
+
"""A tool result, short, so ProbiAI can read an amount or a booking reference from it."""
|
|
37
|
+
if result is None:
|
|
38
|
+
return None
|
|
39
|
+
try:
|
|
40
|
+
text = result if isinstance(result, str) else json.dumps(result, default=str)
|
|
41
|
+
except (TypeError, ValueError):
|
|
42
|
+
text = str(result)
|
|
43
|
+
return text[:500]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def send(copy: dict, key: str | None = None) -> None:
|
|
47
|
+
"""Send one copy in the background. Does nothing without a key."""
|
|
48
|
+
key = key or os.environ.get("PROBIAI_KEY", "")
|
|
49
|
+
if key:
|
|
50
|
+
body = json.dumps({"v": 1, "ts": datetime.now(timezone.utc).isoformat(), **copy}, default=str).encode()
|
|
51
|
+
threading.Thread(target=_post, args=(body, key), name="probiai-copy").start()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _post(body: bytes, key: str) -> None:
|
|
55
|
+
req = urllib.request.Request(f"{URL}/v1/copies", data=body, method="POST",
|
|
56
|
+
headers={"content-type": "application/json", "authorization": f"Bearer {key}"})
|
|
57
|
+
try:
|
|
58
|
+
urllib.request.urlopen(req, timeout=5).close()
|
|
59
|
+
except Exception: # noqa: BLE001 - a copy that can't be sent must never affect the agent
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def claude_hooks(key: str | None = None, model: str | None = None) -> dict:
|
|
64
|
+
"""Claude Agent SDK: every tool call, failed tool call and finished sub-agent."""
|
|
65
|
+
from claude_agent_sdk import HookMatcher
|
|
66
|
+
|
|
67
|
+
def copy(i: dict, status: str) -> dict:
|
|
68
|
+
return {"harness": "claude-agent-sdk", "model": model, "tool": i.get("tool_name"),
|
|
69
|
+
"input": facts(i.get("tool_input") or {}), "status": status, "result": short(i.get("tool_response"))}
|
|
70
|
+
|
|
71
|
+
async def done(i, tool_use_id, context):
|
|
72
|
+
send(copy(i, "ok"), key)
|
|
73
|
+
return {}
|
|
74
|
+
|
|
75
|
+
async def failed(i, tool_use_id, context):
|
|
76
|
+
send(copy(i, "error"), key)
|
|
77
|
+
return {}
|
|
78
|
+
|
|
79
|
+
async def subagent(i, tool_use_id, context):
|
|
80
|
+
send({"harness": "claude-agent-sdk", "model": model,
|
|
81
|
+
"handoff": {"name": i.get("agent_type") or "sub-agent", "via": "own sub-agent"}}, key)
|
|
82
|
+
return {}
|
|
83
|
+
|
|
84
|
+
return {"PostToolUse": [HookMatcher(hooks=[done])], "PostToolUseFailure": [HookMatcher(hooks=[failed])],
|
|
85
|
+
"SubagentStop": [HookMatcher(hooks=[subagent])]}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
from agents import RunHooks as _RunHooks
|
|
90
|
+
except ImportError: # the OpenAI Agents SDK isn't installed: OpenAIHooks is never used then
|
|
91
|
+
_RunHooks = object
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class OpenAIHooks(_RunHooks):
|
|
95
|
+
"""OpenAI Agents SDK: every tool call and every hand-off to another agent."""
|
|
96
|
+
|
|
97
|
+
def __init__(self, key: str | None = None):
|
|
98
|
+
super().__init__()
|
|
99
|
+
self.key = key
|
|
100
|
+
|
|
101
|
+
async def on_tool_end(self, context, agent, tool, result) -> None:
|
|
102
|
+
args = getattr(context, "tool_arguments", None)
|
|
103
|
+
try:
|
|
104
|
+
args = json.loads(args) if isinstance(args, str) else {}
|
|
105
|
+
except ValueError:
|
|
106
|
+
args = {}
|
|
107
|
+
send({"harness": "openai-agents", "model": _model(agent), "tool": getattr(context, "tool_name", None)
|
|
108
|
+
or getattr(tool, "name", "tool"), "input": facts(args), "status": "ok", "result": short(result)}, self.key)
|
|
109
|
+
|
|
110
|
+
async def on_handoff(self, context, from_agent, to_agent) -> None:
|
|
111
|
+
send({"harness": "openai-agents", "model": _model(from_agent),
|
|
112
|
+
"handoff": {"name": getattr(to_agent, "name", "agent"), "via": "handoff"}}, self.key)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _model(agent) -> str | None:
|
|
116
|
+
m = getattr(agent, "model", None)
|
|
117
|
+
return m if isinstance(m, str) else getattr(m, "model", None)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "probiai"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Sends ProbiAI a copy of each action your agent takes. Report-only, no dependencies."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
keywords = ["claude-agent-sdk", "openai-agents", "agents", "telemetry", "probiai"]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Homepage = "https://probiai-personalagent.vercel.app"
|
|
17
|
+
|
|
18
|
+
[tool.hatch.build.targets.wheel]
|
|
19
|
+
only-include = ["probiai.py"]
|