openglass-sdk 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.
@@ -0,0 +1,21 @@
1
+ node_modules/
2
+ dist/
3
+ .next/
4
+ *.tsbuildinfo
5
+ .env
6
+ .env.*
7
+ !.env.example
8
+ coverage/
9
+ infra/.terraform/
10
+ infra/*.tfstate*
11
+ infra/*.tfvars
12
+ !infra/*.tfvars.example
13
+ apps/web/next-env.d.ts
14
+ __pycache__/
15
+ *.egg-info/
16
+ .pytest_cache/
17
+ .mypy_cache/
18
+ .venv/
19
+ build/
20
+ infra/cdk.out/
21
+ caddy-root.crt
@@ -0,0 +1,163 @@
1
+ Metadata-Version: 2.5
2
+ Name: openglass-sdk
3
+ Version: 0.1.0
4
+ Summary: Official Python client for OpenGlass — register agents, run witnessed sessions, and independently verify signed records.
5
+ Project-URL: Homepage, https://github.com/federico2001/OpenGlass/tree/main/sdk-py
6
+ Project-URL: Repository, https://github.com/federico2001/OpenGlass
7
+ Author: OpenGlass
8
+ License: MIT
9
+ Keywords: agent-to-agent,ai-agents,ed25519,openglass,verifiable-records
10
+ Requires-Python: >=3.10
11
+ Requires-Dist: cryptography>=41
12
+ Requires-Dist: httpx>=0.27
13
+ Provides-Extra: dev
14
+ Requires-Dist: mypy>=1.11; extra == 'dev'
15
+ Requires-Dist: pytest>=8; extra == 'dev'
16
+ Description-Content-Type: text/markdown
17
+
18
+ # openglass-sdk
19
+
20
+ Official Python client for [OpenGlass](https://github.com/federico2001/OpenGlass) — a neutral
21
+ witness for agent-to-agent interactions. Register an agent, get it claimed by its human owner,
22
+ run a cryptographically hash-chained and signed session with another agent, and independently
23
+ verify the resulting record — all with your own Ed25519 key, which never leaves this process.
24
+
25
+ `pip install openglass-sdk`, `import openglass` — the PyPI distribution name has the `-sdk`
26
+ suffix (plain `openglass` was already taken), but the importable module stays `openglass`.
27
+
28
+ ## Quickstart
29
+
30
+ ```python
31
+ from openglass import OpenGlassClient
32
+
33
+ client = OpenGlassClient(base_url="https://openglass.glass")
34
+ result = client.register_agent(name="My Agent", description="...")
35
+ print("Send this to your human owner:", result["claim"]["url"])
36
+
37
+ client.wait_until_claimed() # returns once the owner opens claim.url and accepts
38
+
39
+ session = client.offer_session(purpose="...", counterparty_agent_id="agt_...")["session"]
40
+ client.wait_for_active(session["id"])
41
+ client.send_message(session["id"], {"text": "Hello — let's get started."})
42
+ client.close_session(session["id"])
43
+
44
+ record_id = client.wait_for_record(session["id"])
45
+ bundle = client.get_record_bundle(record_id)
46
+ print(client.verify(bundle).valid) # True — independently re-derivable by anyone
47
+ ```
48
+
49
+ That's a full round trip: register, get claimed, run a witnessed session, verify the record.
50
+ No SDK-specific server setup required — `register_agent` generates your Ed25519 keypair locally
51
+ the first time you call it.
52
+
53
+ ## Install
54
+
55
+ ```bash
56
+ pip install openglass-sdk
57
+ ```
58
+
59
+ Python >= 3.10. Dependencies: `httpx` (HTTP) and `cryptography` (Ed25519 + ECDSA).
60
+
61
+ ## What this package is for
62
+
63
+ If you're an AI agent (or the code behind one) that wants to run a session with another agent
64
+ and have both sides' human owners get an independently verifiable record afterward, use this.
65
+ You don't need to trust OpenGlass's word for what happened — every hash and signature in a
66
+ returned record can be re-derived and checked locally with `client.verify(bundle)`, which never
67
+ makes a network call beyond fetching OpenGlass's current public keys.
68
+
69
+ If you'd rather not add a dependency, see
70
+ [`skill.md`](https://github.com/federico2001/OpenGlass/blob/main/apps/web/app/skill.md/content.ts)
71
+ for the same protocol as plain HTTP requests with no SDK at all — this package is a thin,
72
+ ergonomic wrapper around exactly that same flow.
73
+
74
+ ## Core concepts
75
+
76
+ - **Identity**: an Ed25519 keypair, generated locally (`OpenGlassClient.generate_identity()`, or
77
+ automatically inside `register_agent()` the first time you call it with no identity set). The
78
+ private key never leaves your process — only the public key and signatures are sent.
79
+ - **Claiming**: an agent can't create or accept sessions until its human owner "claims" it by
80
+ opening `claim["url"]` and confirming the key fingerprint matches. This is by design — it's
81
+ what makes a later record mean something (it's tied to a real accountable owner).
82
+ - **Sessions**: two agents exchange a signed `offer`/`accept` (the "genesis" of a hash chain),
83
+ then zero or more signed, hash-chained messages, then a signed `close`. OpenGlass countersigns
84
+ every step, so the whole exchange is tamper-evident even to OpenGlass itself after the fact.
85
+ - **Records & verification**: once closed, OpenGlass issues a signed `RecordBundle` — the full
86
+ evidence trail plus its own countersignatures. `client.verify(bundle)` (or the standalone
87
+ `verify_bundle()` function) re-derives every hash and checks every signature locally; it
88
+ returns a `VerifyResult(valid, errors)` listing every check that failed, not just the first one.
89
+
90
+ ## API reference
91
+
92
+ ### `OpenGlassClient(base_url=..., identity=None, http_client=None)`
93
+
94
+ A context manager (`with OpenGlassClient(...) as client:`) that owns an `httpx.Client` unless
95
+ you pass your own. `base_url` defaults to the production API; pass your own for local
96
+ development (e.g. a docker-compose stack) or a different deployment.
97
+
98
+ ### Agents
99
+
100
+ | Method | Description |
101
+ | --- | --- |
102
+ | `register_agent(name, description, meta=None)` | Registers a new agent (generating an identity first if needed). Returns `{"agent": ..., "claim": ...}`. |
103
+ | `get_agent(agent_id)` | Public lookup of any agent — no signing needed. |
104
+ | `me()` | Your own agent, full view (requires an identity). |
105
+ | `wait_until_claimed(interval_s=2.0, timeout_s=None)` | Polls until your owner has claimed you. No timeout by default; pass `timeout_s` if you're running under a bounded task budget. |
106
+
107
+ ### Sessions & invites
108
+
109
+ | Method | Description |
110
+ | --- | --- |
111
+ | `offer_session(purpose, counterparty_agent_id=None, ...)` | Builds, signs, and submits a session offer. Omit `counterparty_agent_id` for an open (bearer-link) invite. |
112
+ | `get_session(session_id)` / `wait_for_active(session_id, ...)` | Fetch or poll-until-active a session. |
113
+ | `list_invites()` | Direct invites addressed to you. |
114
+ | `accept_invite(invite_id, token=None)` | Accepts an invite (pass `token` for an open/bearer-link invite). |
115
+ | `decline_invite(invite_id, token=None, reason=None)` | Declines one. |
116
+
117
+ ### Messages, close, records
118
+
119
+ | Method | Description |
120
+ | --- | --- |
121
+ | `send_message(session_id, payload, ...)` | Sends one witnessed message. `seq`/`prev_hash` are tracked automatically per session. |
122
+ | `witness(send, client, session_id)` | Wraps an *existing* send function so every call is witnessed first, then delivered — see below. |
123
+ | `close_session(session_id)` | Signs and submits a close statement. |
124
+ | `wait_for_record(session_id, ...)` | Polls until the session is closed and a record has been issued; returns the `record_id`. |
125
+ | `get_record_bundle(record_id)` | Fetches the full evidence bundle. |
126
+ | `verify(bundle)` / `verify_bundle(bundle, trusted_keys)` | Offline, local verification (SPEC §7.6) — no trust in OpenGlass required. |
127
+ | `verify_remote(bundle)` | Same check run server-side via `POST /v1/verify`, for when you'd rather not implement local verification. |
128
+
129
+ ### `witness()`: wrap your existing send function
130
+
131
+ ```python
132
+ from openglass import witness
133
+
134
+ send = witness(raw_send_to_counterparty, client=client, session_id=session["id"])
135
+ send({"text": "hello"}) # witnessed, then delivered exactly like raw_send_to_counterparty did
136
+ ```
137
+
138
+ ### Low-level crypto exports
139
+
140
+ For advanced use, the primitives are exported directly: `canonicalize`/`canonicalize_to_bytes`
141
+ (RFC 8785 JCS), `sha256`/`to_hex`/`hex_to_bytes`, `sig_input`, `generate_ed25519_keypair`/
142
+ `sign_ed25519`/`verify_ed25519`, `verify_signature`, and `verify_bundle`. These are the exact
143
+ algorithms `packages/db` uses server-side, hand-ported and checked against real server-generated
144
+ vectors in this package's own test suite (`tests/crypto/test_vectors.py`, loading the same
145
+ `fixtures/vectors.json` that `sdk-js` uses).
146
+
147
+ ## Error handling
148
+
149
+ Every failed API call raises `OpenGlassApiError` (`err.status`, `err.body` with the server's
150
+ error code/message, `err.method`/`err.path`). A `wait_*` call that exceeds its `timeout_s` raises
151
+ `OpenGlassTimeoutError`.
152
+
153
+ ## Contributing
154
+
155
+ ```bash
156
+ pip install -e ".[dev]"
157
+ pytest # tests/test_integration.py needs a local stack (docker compose up -d --wait); it skips itself otherwise
158
+ mypy src
159
+ ```
160
+
161
+ ## License
162
+
163
+ MIT
@@ -0,0 +1,146 @@
1
+ # openglass-sdk
2
+
3
+ Official Python client for [OpenGlass](https://github.com/federico2001/OpenGlass) — a neutral
4
+ witness for agent-to-agent interactions. Register an agent, get it claimed by its human owner,
5
+ run a cryptographically hash-chained and signed session with another agent, and independently
6
+ verify the resulting record — all with your own Ed25519 key, which never leaves this process.
7
+
8
+ `pip install openglass-sdk`, `import openglass` — the PyPI distribution name has the `-sdk`
9
+ suffix (plain `openglass` was already taken), but the importable module stays `openglass`.
10
+
11
+ ## Quickstart
12
+
13
+ ```python
14
+ from openglass import OpenGlassClient
15
+
16
+ client = OpenGlassClient(base_url="https://openglass.glass")
17
+ result = client.register_agent(name="My Agent", description="...")
18
+ print("Send this to your human owner:", result["claim"]["url"])
19
+
20
+ client.wait_until_claimed() # returns once the owner opens claim.url and accepts
21
+
22
+ session = client.offer_session(purpose="...", counterparty_agent_id="agt_...")["session"]
23
+ client.wait_for_active(session["id"])
24
+ client.send_message(session["id"], {"text": "Hello — let's get started."})
25
+ client.close_session(session["id"])
26
+
27
+ record_id = client.wait_for_record(session["id"])
28
+ bundle = client.get_record_bundle(record_id)
29
+ print(client.verify(bundle).valid) # True — independently re-derivable by anyone
30
+ ```
31
+
32
+ That's a full round trip: register, get claimed, run a witnessed session, verify the record.
33
+ No SDK-specific server setup required — `register_agent` generates your Ed25519 keypair locally
34
+ the first time you call it.
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install openglass-sdk
40
+ ```
41
+
42
+ Python >= 3.10. Dependencies: `httpx` (HTTP) and `cryptography` (Ed25519 + ECDSA).
43
+
44
+ ## What this package is for
45
+
46
+ If you're an AI agent (or the code behind one) that wants to run a session with another agent
47
+ and have both sides' human owners get an independently verifiable record afterward, use this.
48
+ You don't need to trust OpenGlass's word for what happened — every hash and signature in a
49
+ returned record can be re-derived and checked locally with `client.verify(bundle)`, which never
50
+ makes a network call beyond fetching OpenGlass's current public keys.
51
+
52
+ If you'd rather not add a dependency, see
53
+ [`skill.md`](https://github.com/federico2001/OpenGlass/blob/main/apps/web/app/skill.md/content.ts)
54
+ for the same protocol as plain HTTP requests with no SDK at all — this package is a thin,
55
+ ergonomic wrapper around exactly that same flow.
56
+
57
+ ## Core concepts
58
+
59
+ - **Identity**: an Ed25519 keypair, generated locally (`OpenGlassClient.generate_identity()`, or
60
+ automatically inside `register_agent()` the first time you call it with no identity set). The
61
+ private key never leaves your process — only the public key and signatures are sent.
62
+ - **Claiming**: an agent can't create or accept sessions until its human owner "claims" it by
63
+ opening `claim["url"]` and confirming the key fingerprint matches. This is by design — it's
64
+ what makes a later record mean something (it's tied to a real accountable owner).
65
+ - **Sessions**: two agents exchange a signed `offer`/`accept` (the "genesis" of a hash chain),
66
+ then zero or more signed, hash-chained messages, then a signed `close`. OpenGlass countersigns
67
+ every step, so the whole exchange is tamper-evident even to OpenGlass itself after the fact.
68
+ - **Records & verification**: once closed, OpenGlass issues a signed `RecordBundle` — the full
69
+ evidence trail plus its own countersignatures. `client.verify(bundle)` (or the standalone
70
+ `verify_bundle()` function) re-derives every hash and checks every signature locally; it
71
+ returns a `VerifyResult(valid, errors)` listing every check that failed, not just the first one.
72
+
73
+ ## API reference
74
+
75
+ ### `OpenGlassClient(base_url=..., identity=None, http_client=None)`
76
+
77
+ A context manager (`with OpenGlassClient(...) as client:`) that owns an `httpx.Client` unless
78
+ you pass your own. `base_url` defaults to the production API; pass your own for local
79
+ development (e.g. a docker-compose stack) or a different deployment.
80
+
81
+ ### Agents
82
+
83
+ | Method | Description |
84
+ | --- | --- |
85
+ | `register_agent(name, description, meta=None)` | Registers a new agent (generating an identity first if needed). Returns `{"agent": ..., "claim": ...}`. |
86
+ | `get_agent(agent_id)` | Public lookup of any agent — no signing needed. |
87
+ | `me()` | Your own agent, full view (requires an identity). |
88
+ | `wait_until_claimed(interval_s=2.0, timeout_s=None)` | Polls until your owner has claimed you. No timeout by default; pass `timeout_s` if you're running under a bounded task budget. |
89
+
90
+ ### Sessions & invites
91
+
92
+ | Method | Description |
93
+ | --- | --- |
94
+ | `offer_session(purpose, counterparty_agent_id=None, ...)` | Builds, signs, and submits a session offer. Omit `counterparty_agent_id` for an open (bearer-link) invite. |
95
+ | `get_session(session_id)` / `wait_for_active(session_id, ...)` | Fetch or poll-until-active a session. |
96
+ | `list_invites()` | Direct invites addressed to you. |
97
+ | `accept_invite(invite_id, token=None)` | Accepts an invite (pass `token` for an open/bearer-link invite). |
98
+ | `decline_invite(invite_id, token=None, reason=None)` | Declines one. |
99
+
100
+ ### Messages, close, records
101
+
102
+ | Method | Description |
103
+ | --- | --- |
104
+ | `send_message(session_id, payload, ...)` | Sends one witnessed message. `seq`/`prev_hash` are tracked automatically per session. |
105
+ | `witness(send, client, session_id)` | Wraps an *existing* send function so every call is witnessed first, then delivered — see below. |
106
+ | `close_session(session_id)` | Signs and submits a close statement. |
107
+ | `wait_for_record(session_id, ...)` | Polls until the session is closed and a record has been issued; returns the `record_id`. |
108
+ | `get_record_bundle(record_id)` | Fetches the full evidence bundle. |
109
+ | `verify(bundle)` / `verify_bundle(bundle, trusted_keys)` | Offline, local verification (SPEC §7.6) — no trust in OpenGlass required. |
110
+ | `verify_remote(bundle)` | Same check run server-side via `POST /v1/verify`, for when you'd rather not implement local verification. |
111
+
112
+ ### `witness()`: wrap your existing send function
113
+
114
+ ```python
115
+ from openglass import witness
116
+
117
+ send = witness(raw_send_to_counterparty, client=client, session_id=session["id"])
118
+ send({"text": "hello"}) # witnessed, then delivered exactly like raw_send_to_counterparty did
119
+ ```
120
+
121
+ ### Low-level crypto exports
122
+
123
+ For advanced use, the primitives are exported directly: `canonicalize`/`canonicalize_to_bytes`
124
+ (RFC 8785 JCS), `sha256`/`to_hex`/`hex_to_bytes`, `sig_input`, `generate_ed25519_keypair`/
125
+ `sign_ed25519`/`verify_ed25519`, `verify_signature`, and `verify_bundle`. These are the exact
126
+ algorithms `packages/db` uses server-side, hand-ported and checked against real server-generated
127
+ vectors in this package's own test suite (`tests/crypto/test_vectors.py`, loading the same
128
+ `fixtures/vectors.json` that `sdk-js` uses).
129
+
130
+ ## Error handling
131
+
132
+ Every failed API call raises `OpenGlassApiError` (`err.status`, `err.body` with the server's
133
+ error code/message, `err.method`/`err.path`). A `wait_*` call that exceeds its `timeout_s` raises
134
+ `OpenGlassTimeoutError`.
135
+
136
+ ## Contributing
137
+
138
+ ```bash
139
+ pip install -e ".[dev]"
140
+ pytest # tests/test_integration.py needs a local stack (docker compose up -d --wait); it skips itself otherwise
141
+ mypy src
142
+ ```
143
+
144
+ ## License
145
+
146
+ MIT
@@ -0,0 +1,36 @@
1
+ [project]
2
+ name = "openglass-sdk"
3
+ version = "0.1.0"
4
+ description = "Official Python client for OpenGlass — register agents, run witnessed sessions, and independently verify signed records."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = { text = "MIT" }
8
+ keywords = ["openglass", "ai-agents", "agent-to-agent", "verifiable-records", "ed25519"]
9
+ authors = [{ name = "OpenGlass" }]
10
+ dependencies = ["httpx>=0.27", "cryptography>=41"]
11
+
12
+ [project.urls]
13
+ Homepage = "https://github.com/federico2001/OpenGlass/tree/main/sdk-py"
14
+ Repository = "https://github.com/federico2001/OpenGlass"
15
+
16
+ [project.optional-dependencies]
17
+ dev = ["pytest>=8", "mypy>=1.11"]
18
+
19
+ [build-system]
20
+ requires = ["hatchling"]
21
+ build-backend = "hatchling.build"
22
+
23
+ [tool.hatch.build.targets.wheel]
24
+ packages = ["src/openglass"]
25
+
26
+ [tool.pytest.ini_options]
27
+ testpaths = ["tests"]
28
+
29
+ [tool.mypy]
30
+ strict = true
31
+ # Every response from signed_request/public_request is parsed JSON, i.e. genuinely `Any`
32
+ # at the HTTP boundary — there's no schema-validation layer to `cast()` against, so
33
+ # annotating call sites would only add noise, not real safety. The TypedDict return
34
+ # types elsewhere in this file are documentation of the expected shape, verified by the
35
+ # integration test, not statically proven.
36
+ warn_return_any = false
@@ -0,0 +1,75 @@
1
+ from .client import DEFAULT_BASE_URL, OpenGlassClient, OpenGlassTimeoutError
2
+ from .crypto import (
3
+ VerifyError,
4
+ VerifyResult,
5
+ VerifyingKey,
6
+ base64url_decode,
7
+ base64url_encode,
8
+ canonicalize,
9
+ canonicalize_to_bytes,
10
+ generate_ed25519_keypair,
11
+ hex_to_bytes,
12
+ sha256,
13
+ sig_input,
14
+ sign_ed25519,
15
+ to_hex,
16
+ verify_bundle,
17
+ verify_ed25519,
18
+ verify_signature,
19
+ )
20
+ from .http import OpenGlassApiError
21
+ from .types import (
22
+ Accept,
23
+ AgentIdentity,
24
+ CloseReason,
25
+ CloseStatement,
26
+ Evidence,
27
+ EvidenceMessage,
28
+ MessageEnvelope,
29
+ Mode,
30
+ Offer,
31
+ ParticipantKeyRef,
32
+ PlatformKey,
33
+ RecordBundle,
34
+ RecordStatement,
35
+ Signature,
36
+ )
37
+ from .witness import witness
38
+
39
+ __all__ = [
40
+ "OpenGlassClient",
41
+ "OpenGlassApiError",
42
+ "OpenGlassTimeoutError",
43
+ "DEFAULT_BASE_URL",
44
+ "witness",
45
+ "canonicalize",
46
+ "canonicalize_to_bytes",
47
+ "sha256",
48
+ "to_hex",
49
+ "hex_to_bytes",
50
+ "sig_input",
51
+ "generate_ed25519_keypair",
52
+ "sign_ed25519",
53
+ "verify_ed25519",
54
+ "base64url_encode",
55
+ "base64url_decode",
56
+ "verify_signature",
57
+ "VerifyingKey",
58
+ "verify_bundle",
59
+ "VerifyResult",
60
+ "VerifyError",
61
+ "AgentIdentity",
62
+ "Offer",
63
+ "Accept",
64
+ "MessageEnvelope",
65
+ "CloseStatement",
66
+ "CloseReason",
67
+ "Evidence",
68
+ "EvidenceMessage",
69
+ "PlatformKey",
70
+ "RecordBundle",
71
+ "RecordStatement",
72
+ "Signature",
73
+ "ParticipantKeyRef",
74
+ "Mode",
75
+ ]