disclarion 0.2.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,32 @@
1
+ # Secrets — real env files only, *.example templates stay tracked
2
+ .env
3
+ .env.local
4
+ .env.*.local
5
+ .env.production
6
+ .env.development
7
+
8
+ # Node / Next.js
9
+ node_modules/
10
+ .next/
11
+ .vercel/
12
+ *.tsbuildinfo
13
+ npm-debug.log*
14
+
15
+ # Python
16
+ .venv/
17
+ venv/
18
+ __pycache__/
19
+ *.pyc
20
+ *.egg-info/
21
+ .pytest_cache/
22
+
23
+ # Build output
24
+ dist/
25
+ build/
26
+
27
+ # Supabase CLI local state
28
+ supabase/.branches/
29
+ supabase/.temp/
30
+
31
+ # OS
32
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Disclarion
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.
@@ -0,0 +1,128 @@
1
+ Metadata-Version: 2.4
2
+ Name: disclarion
3
+ Version: 0.2.0
4
+ Summary: AI disclosure logging SDK for Disclarion
5
+ Author-email: Disclarion <hello@disclarion.com>
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.9
9
+ Requires-Dist: anthropic>=0.34
10
+ Requires-Dist: google-genai>=1.0
11
+ Requires-Dist: openai>=1.0
12
+ Requires-Dist: requests>=2.28
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=7.0; extra == 'dev'
15
+ Description-Content-Type: text/markdown
16
+
17
+ # disclarion
18
+
19
+ AI disclosure logging SDK. Wraps your LLM provider responses and reports
20
+ standardized disclosure/labeling metadata to Disclarion.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ pip install disclarion
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ```python
31
+ from disclarion import Disclarion
32
+ import openai
33
+
34
+ client = openai.OpenAI()
35
+ dc = Disclarion(api_key="dcl_live_...")
36
+
37
+ response = client.chat.completions.create(
38
+ model="gpt-4o-mini",
39
+ messages=[{"role": "user", "content": "hello"}],
40
+ )
41
+ dc.track(response, session_id="session-123")
42
+ ```
43
+
44
+ `track()` never modifies `response` by default — it attaches a
45
+ `disclarion_meta` dict to it instead, and your own UI decides what to show:
46
+
47
+ ```python
48
+ tracked = dc.track(response, session_id="session-123")
49
+ tracked.disclarion_meta
50
+ # {"ai_generated": True, "is_first_message": True, "disclosed": False, "logged_at": "..."}
51
+ ```
52
+
53
+ `is_first_message` is what a host-app disclosure widget should key off of —
54
+ show a one-time blocking notice on `True`, keep a small persistent badge up
55
+ for the rest of the session on `False`. See `widget/` for a ready React and
56
+ vanilla-JS implementation of exactly that pattern.
57
+
58
+ This is the `disclosure_mode="modal_once"` default, set on the client, not
59
+ per call:
60
+
61
+ - **`modal_once`** (default) — never touches response content; only
62
+ `disclarion_meta` changes, as above.
63
+ - **`inline_every_message`** — the SDK's original behavior, kept for
64
+ backward compatibility. Despite the name, it still only rewrites the
65
+ response's own content on a session's *first* message (never later
66
+ ones) to append/prepend `disclosure_text`; `disclarion_meta["disclosed"]`
67
+ reflects whether that happened.
68
+ - **`off`** — pure logging, no disclosure signal at all:
69
+ `disclarion_meta` omits `is_first_message`/`disclosed` entirely. Use
70
+ this if your product already has its own unrelated AI-disclosure UI.
71
+
72
+ ```python
73
+ dc = Disclarion(api_key="dcl_live_...", disclosure_mode="inline_every_message")
74
+ ```
75
+
76
+ Regardless of `disclosure_mode`, every `track()` call is still logged to
77
+ Disclarion's audit trail (`interaction_logs`) exactly the same way — the UI
78
+ mode only changes what happens to `response` and `disclarion_meta`, never
79
+ what gets recorded for compliance.
80
+
81
+ `track()` makes one fast synchronous attempt (that's what makes
82
+ `is_first_message` available immediately) and never retries inline — a
83
+ slow or erroring Disclarion backend can never turn into multi-second
84
+ latency on your own request path. Anything worth retrying (a network
85
+ error, a `5xx`, a transient `429`) is handed to a background thread with
86
+ a bounded queue (up to 1000 pending logs, oldest dropped first if your
87
+ process is generating logs faster than Disclarion can accept them) that
88
+ retries with capped, jittered exponential backoff, honoring a `429`'s
89
+ `Retry-After` header when present. The queue is flushed on normal
90
+ process exit, `SIGINT`, and `SIGTERM` (bounded to a couple of seconds, so
91
+ shutdown is never blocked indefinitely by a backend that's still down).
92
+
93
+ A `429` that means "you've exceeded your monthly plan quota" is handled
94
+ differently from a transient rate limit: retrying it is pointless until
95
+ next period, so the SDK opens a circuit breaker instead — every `track()`
96
+ call skips the network entirely (sub-millisecond) until an hourly (then
97
+ exponentially longer, capped at 24h) cooldown elapses and one probe
98
+ request checks whether it's resolved. One `logging.warning` marks each
99
+ state change (opened/resolved), not one per call, so a month-long outage
100
+ doesn't spam your logs. Call `dc.get_stats()` any time for the queue size,
101
+ circuit state, and delivery counters.
102
+
103
+ An invalid or revoked API key is different from all of the above: the
104
+ backend is reachable and is explicitly rejecting it, which is a setup
105
+ mistake you need to see, so that raises `disclarion.AuthenticationError`
106
+ immediately — no retry, no circuit-breaking, no silent fallback.
107
+ Unsupported response types raise a `ValueError` immediately for the same
108
+ reason: both are programming/config errors to fix, not a runtime
109
+ condition to swallow.
110
+
111
+ OpenAI (`ChatCompletion`), Anthropic (`Message`), and Gemini
112
+ (`GenerateContentResponse`) responses are all supported — see
113
+ `disclarion/adapters/`.
114
+
115
+ ## Testing without a live backend
116
+
117
+ To exercise `_send_log()` end to end without hitting the real
118
+ `api.disclarion.com`:
119
+
120
+ ```bash
121
+ python scripts/mock_server.py # terminal 1 - fake /v1/logs endpoint
122
+ python scripts/manual_test.py # terminal 2 - sends a fake ChatCompletion
123
+ ```
124
+
125
+ The mock server prints every received payload and the `Authorization`
126
+ header so you can confirm the normalized log shape is correct. See
127
+ `scripts/README.md` for details, or `tests/` for the automated unit tests
128
+ (`pytest`) which mock the HTTP layer instead of needing a running server.
@@ -0,0 +1,112 @@
1
+ # disclarion
2
+
3
+ AI disclosure logging SDK. Wraps your LLM provider responses and reports
4
+ standardized disclosure/labeling metadata to Disclarion.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ pip install disclarion
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```python
15
+ from disclarion import Disclarion
16
+ import openai
17
+
18
+ client = openai.OpenAI()
19
+ dc = Disclarion(api_key="dcl_live_...")
20
+
21
+ response = client.chat.completions.create(
22
+ model="gpt-4o-mini",
23
+ messages=[{"role": "user", "content": "hello"}],
24
+ )
25
+ dc.track(response, session_id="session-123")
26
+ ```
27
+
28
+ `track()` never modifies `response` by default — it attaches a
29
+ `disclarion_meta` dict to it instead, and your own UI decides what to show:
30
+
31
+ ```python
32
+ tracked = dc.track(response, session_id="session-123")
33
+ tracked.disclarion_meta
34
+ # {"ai_generated": True, "is_first_message": True, "disclosed": False, "logged_at": "..."}
35
+ ```
36
+
37
+ `is_first_message` is what a host-app disclosure widget should key off of —
38
+ show a one-time blocking notice on `True`, keep a small persistent badge up
39
+ for the rest of the session on `False`. See `widget/` for a ready React and
40
+ vanilla-JS implementation of exactly that pattern.
41
+
42
+ This is the `disclosure_mode="modal_once"` default, set on the client, not
43
+ per call:
44
+
45
+ - **`modal_once`** (default) — never touches response content; only
46
+ `disclarion_meta` changes, as above.
47
+ - **`inline_every_message`** — the SDK's original behavior, kept for
48
+ backward compatibility. Despite the name, it still only rewrites the
49
+ response's own content on a session's *first* message (never later
50
+ ones) to append/prepend `disclosure_text`; `disclarion_meta["disclosed"]`
51
+ reflects whether that happened.
52
+ - **`off`** — pure logging, no disclosure signal at all:
53
+ `disclarion_meta` omits `is_first_message`/`disclosed` entirely. Use
54
+ this if your product already has its own unrelated AI-disclosure UI.
55
+
56
+ ```python
57
+ dc = Disclarion(api_key="dcl_live_...", disclosure_mode="inline_every_message")
58
+ ```
59
+
60
+ Regardless of `disclosure_mode`, every `track()` call is still logged to
61
+ Disclarion's audit trail (`interaction_logs`) exactly the same way — the UI
62
+ mode only changes what happens to `response` and `disclarion_meta`, never
63
+ what gets recorded for compliance.
64
+
65
+ `track()` makes one fast synchronous attempt (that's what makes
66
+ `is_first_message` available immediately) and never retries inline — a
67
+ slow or erroring Disclarion backend can never turn into multi-second
68
+ latency on your own request path. Anything worth retrying (a network
69
+ error, a `5xx`, a transient `429`) is handed to a background thread with
70
+ a bounded queue (up to 1000 pending logs, oldest dropped first if your
71
+ process is generating logs faster than Disclarion can accept them) that
72
+ retries with capped, jittered exponential backoff, honoring a `429`'s
73
+ `Retry-After` header when present. The queue is flushed on normal
74
+ process exit, `SIGINT`, and `SIGTERM` (bounded to a couple of seconds, so
75
+ shutdown is never blocked indefinitely by a backend that's still down).
76
+
77
+ A `429` that means "you've exceeded your monthly plan quota" is handled
78
+ differently from a transient rate limit: retrying it is pointless until
79
+ next period, so the SDK opens a circuit breaker instead — every `track()`
80
+ call skips the network entirely (sub-millisecond) until an hourly (then
81
+ exponentially longer, capped at 24h) cooldown elapses and one probe
82
+ request checks whether it's resolved. One `logging.warning` marks each
83
+ state change (opened/resolved), not one per call, so a month-long outage
84
+ doesn't spam your logs. Call `dc.get_stats()` any time for the queue size,
85
+ circuit state, and delivery counters.
86
+
87
+ An invalid or revoked API key is different from all of the above: the
88
+ backend is reachable and is explicitly rejecting it, which is a setup
89
+ mistake you need to see, so that raises `disclarion.AuthenticationError`
90
+ immediately — no retry, no circuit-breaking, no silent fallback.
91
+ Unsupported response types raise a `ValueError` immediately for the same
92
+ reason: both are programming/config errors to fix, not a runtime
93
+ condition to swallow.
94
+
95
+ OpenAI (`ChatCompletion`), Anthropic (`Message`), and Gemini
96
+ (`GenerateContentResponse`) responses are all supported — see
97
+ `disclarion/adapters/`.
98
+
99
+ ## Testing without a live backend
100
+
101
+ To exercise `_send_log()` end to end without hitting the real
102
+ `api.disclarion.com`:
103
+
104
+ ```bash
105
+ python scripts/mock_server.py # terminal 1 - fake /v1/logs endpoint
106
+ python scripts/manual_test.py # terminal 2 - sends a fake ChatCompletion
107
+ ```
108
+
109
+ The mock server prints every received payload and the `Authorization`
110
+ header so you can confirm the normalized log shape is correct. See
111
+ `scripts/README.md` for details, or `tests/` for the automated unit tests
112
+ (`pytest`) which mock the HTTP layer instead of needing a running server.
@@ -0,0 +1,7 @@
1
+ from .client import Disclarion
2
+ from .exceptions import AuthenticationError
3
+ from .models import NormalizedInteraction
4
+
5
+ __all__ = ["Disclarion", "NormalizedInteraction", "AuthenticationError"]
6
+
7
+ __version__ = "0.2.0"