agent-first-data 0.4.1__tar.gz → 0.5.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.
- {agent_first_data-0.4.1 → agent_first_data-0.5.0}/PKG-INFO +12 -5
- {agent_first_data-0.4.1 → agent_first_data-0.5.0}/README.md +11 -4
- {agent_first_data-0.4.1 → agent_first_data-0.5.0}/agent_first_data/__init__.py +4 -0
- {agent_first_data-0.4.1 → agent_first_data-0.5.0}/agent_first_data/format.py +65 -3
- {agent_first_data-0.4.1 → agent_first_data-0.5.0}/agent_first_data.egg-info/PKG-INFO +12 -5
- {agent_first_data-0.4.1 → agent_first_data-0.5.0}/pyproject.toml +1 -1
- {agent_first_data-0.4.1 → agent_first_data-0.5.0}/tests/test_afdata_logging.py +6 -0
- {agent_first_data-0.4.1 → agent_first_data-0.5.0}/tests/test_format.py +52 -0
- {agent_first_data-0.4.1 → agent_first_data-0.5.0}/agent_first_data/afdata_logging.py +0 -0
- {agent_first_data-0.4.1 → agent_first_data-0.5.0}/agent_first_data/cli.py +0 -0
- {agent_first_data-0.4.1 → agent_first_data-0.5.0}/agent_first_data.egg-info/SOURCES.txt +0 -0
- {agent_first_data-0.4.1 → agent_first_data-0.5.0}/agent_first_data.egg-info/dependency_links.txt +0 -0
- {agent_first_data-0.4.1 → agent_first_data-0.5.0}/agent_first_data.egg-info/top_level.txt +0 -0
- {agent_first_data-0.4.1 → agent_first_data-0.5.0}/setup.cfg +0 -0
- {agent_first_data-0.4.1 → agent_first_data-0.5.0}/tests/test_cli.py +0 -0
- {agent_first_data-0.4.1 → agent_first_data-0.5.0}/tests/test_no_stderr_policy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-first-data
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates for AI agents
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Repository, https://github.com/cmnspore/agent-first-data
|
|
@@ -74,11 +74,11 @@ Plain: args.input_path=/data/backup.tar.gz code=log event=startup config.max_fil
|
|
|
74
74
|
|
|
75
75
|
## API Reference
|
|
76
76
|
|
|
77
|
-
Total: **
|
|
77
|
+
Total: **13 public APIs and 2 types** + **AFDATA logging** (3 protocol builders + 4 output functions + 1 internal + 1 utility + 4 CLI helpers + `OutputFormat` + `RedactionPolicy`)
|
|
78
78
|
|
|
79
79
|
### Protocol Builders (returns dict)
|
|
80
80
|
|
|
81
|
-
Build AFDATA protocol structures. Return dict objects for
|
|
81
|
+
Build AFDATA protocol structures. Return dict objects for transport payloads.
|
|
82
82
|
|
|
83
83
|
```python
|
|
84
84
|
# Success (result)
|
|
@@ -91,7 +91,7 @@ build_json_error(message: str, trace: Any = None) -> dict
|
|
|
91
91
|
build_json(code: str, fields: Any, trace: Any = None) -> dict
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
**Use case:**
|
|
94
|
+
**Use case:** structured protocol payloads (frameworks automatically serialize)
|
|
95
95
|
|
|
96
96
|
**Example:**
|
|
97
97
|
```python
|
|
@@ -128,14 +128,21 @@ not_found = build_json(
|
|
|
128
128
|
|
|
129
129
|
### CLI/Log Output (returns str)
|
|
130
130
|
|
|
131
|
-
Format values for CLI output and logs.
|
|
131
|
+
Format values for CLI output and logs. `output_json` uses full `_secret` redaction by default. `output_json_with` supports explicit scoped policies. YAML and Plain always redact `_secret` and apply human-readable formatting.
|
|
132
132
|
|
|
133
133
|
```python
|
|
134
134
|
output_json(value: Any) -> str # Single-line JSON, original keys, for programs/logs
|
|
135
|
+
output_json_with(value: Any, redaction_policy: RedactionPolicy) -> str
|
|
135
136
|
output_yaml(value: Any) -> str # Multi-line YAML, keys stripped, values formatted
|
|
136
137
|
output_plain(value: Any) -> str # Single-line logfmt, keys stripped, values formatted
|
|
137
138
|
```
|
|
138
139
|
|
|
140
|
+
```python
|
|
141
|
+
class RedactionPolicy(enum.Enum):
|
|
142
|
+
RedactionTraceOnly = "RedactionTraceOnly"
|
|
143
|
+
RedactionNone = "RedactionNone"
|
|
144
|
+
```
|
|
145
|
+
|
|
139
146
|
**Example:**
|
|
140
147
|
```python
|
|
141
148
|
from agent_first_data import *
|
|
@@ -65,11 +65,11 @@ Plain: args.input_path=/data/backup.tar.gz code=log event=startup config.max_fil
|
|
|
65
65
|
|
|
66
66
|
## API Reference
|
|
67
67
|
|
|
68
|
-
Total: **
|
|
68
|
+
Total: **13 public APIs and 2 types** + **AFDATA logging** (3 protocol builders + 4 output functions + 1 internal + 1 utility + 4 CLI helpers + `OutputFormat` + `RedactionPolicy`)
|
|
69
69
|
|
|
70
70
|
### Protocol Builders (returns dict)
|
|
71
71
|
|
|
72
|
-
Build AFDATA protocol structures. Return dict objects for
|
|
72
|
+
Build AFDATA protocol structures. Return dict objects for transport payloads.
|
|
73
73
|
|
|
74
74
|
```python
|
|
75
75
|
# Success (result)
|
|
@@ -82,7 +82,7 @@ build_json_error(message: str, trace: Any = None) -> dict
|
|
|
82
82
|
build_json(code: str, fields: Any, trace: Any = None) -> dict
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
**Use case:**
|
|
85
|
+
**Use case:** structured protocol payloads (frameworks automatically serialize)
|
|
86
86
|
|
|
87
87
|
**Example:**
|
|
88
88
|
```python
|
|
@@ -119,14 +119,21 @@ not_found = build_json(
|
|
|
119
119
|
|
|
120
120
|
### CLI/Log Output (returns str)
|
|
121
121
|
|
|
122
|
-
Format values for CLI output and logs.
|
|
122
|
+
Format values for CLI output and logs. `output_json` uses full `_secret` redaction by default. `output_json_with` supports explicit scoped policies. YAML and Plain always redact `_secret` and apply human-readable formatting.
|
|
123
123
|
|
|
124
124
|
```python
|
|
125
125
|
output_json(value: Any) -> str # Single-line JSON, original keys, for programs/logs
|
|
126
|
+
output_json_with(value: Any, redaction_policy: RedactionPolicy) -> str
|
|
126
127
|
output_yaml(value: Any) -> str # Multi-line YAML, keys stripped, values formatted
|
|
127
128
|
output_plain(value: Any) -> str # Single-line logfmt, keys stripped, values formatted
|
|
128
129
|
```
|
|
129
130
|
|
|
131
|
+
```python
|
|
132
|
+
class RedactionPolicy(enum.Enum):
|
|
133
|
+
RedactionTraceOnly = "RedactionTraceOnly"
|
|
134
|
+
RedactionNone = "RedactionNone"
|
|
135
|
+
```
|
|
136
|
+
|
|
130
137
|
**Example:**
|
|
131
138
|
```python
|
|
132
139
|
from agent_first_data import *
|
|
@@ -4,7 +4,9 @@ from agent_first_data.format import (
|
|
|
4
4
|
build_json_ok,
|
|
5
5
|
build_json_error,
|
|
6
6
|
build_json,
|
|
7
|
+
RedactionPolicy,
|
|
7
8
|
output_json,
|
|
9
|
+
output_json_with,
|
|
8
10
|
output_yaml,
|
|
9
11
|
output_plain,
|
|
10
12
|
internal_redact_secrets,
|
|
@@ -33,7 +35,9 @@ __all__ = [
|
|
|
33
35
|
"build_json_ok",
|
|
34
36
|
"build_json_error",
|
|
35
37
|
"build_json",
|
|
38
|
+
"RedactionPolicy",
|
|
36
39
|
"output_json",
|
|
40
|
+
"output_json_with",
|
|
37
41
|
"output_yaml",
|
|
38
42
|
"output_plain",
|
|
39
43
|
"internal_redact_secrets",
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"""AFDATA output formatting and protocol templates.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
9 public APIs and 1 type: 3 protocol builders + 4 output formatters + 1 redaction + 1 utility + RedactionPolicy.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
import copy
|
|
9
8
|
import json
|
|
9
|
+
import math
|
|
10
10
|
from datetime import datetime, timezone
|
|
11
|
+
from enum import Enum
|
|
11
12
|
from typing import Any
|
|
12
13
|
|
|
13
14
|
|
|
@@ -45,16 +46,28 @@ def build_json(code: str, fields: Any, trace: Any = None) -> dict:
|
|
|
45
46
|
# Public API: Output Formatters
|
|
46
47
|
# ═══════════════════════════════════════════
|
|
47
48
|
|
|
49
|
+
class RedactionPolicy(str, Enum):
|
|
50
|
+
RedactionTraceOnly = "RedactionTraceOnly"
|
|
51
|
+
RedactionNone = "RedactionNone"
|
|
52
|
+
|
|
48
53
|
|
|
49
54
|
def output_json(value: Any) -> str:
|
|
50
55
|
"""Format as single-line JSON. Secrets redacted, original keys, raw values."""
|
|
51
|
-
v =
|
|
56
|
+
v = _sanitize_for_json(value)
|
|
52
57
|
_redact_secrets(v)
|
|
53
58
|
return json.dumps(v, ensure_ascii=False, separators=(",", ":"))
|
|
54
59
|
|
|
55
60
|
|
|
61
|
+
def output_json_with(value: Any, redaction_policy: RedactionPolicy) -> str:
|
|
62
|
+
"""Format as single-line JSON with explicit redaction policy."""
|
|
63
|
+
v = _sanitize_for_json(value)
|
|
64
|
+
_apply_redaction_policy(v, redaction_policy)
|
|
65
|
+
return json.dumps(v, ensure_ascii=False, separators=(",", ":"))
|
|
66
|
+
|
|
67
|
+
|
|
56
68
|
def output_yaml(value: Any) -> str:
|
|
57
69
|
"""Format as multi-line YAML. Keys stripped, values formatted, secrets redacted."""
|
|
70
|
+
value = _sanitize_for_json(value)
|
|
58
71
|
lines = ["---"]
|
|
59
72
|
_render_yaml_processed(value, 0, lines)
|
|
60
73
|
return "\n".join(lines)
|
|
@@ -62,6 +75,7 @@ def output_yaml(value: Any) -> str:
|
|
|
62
75
|
|
|
63
76
|
def output_plain(value: Any) -> str:
|
|
64
77
|
"""Format as single-line logfmt. Keys stripped, values formatted, secrets redacted."""
|
|
78
|
+
value = _sanitize_for_json(value)
|
|
65
79
|
pairs: list[tuple[str, str]] = []
|
|
66
80
|
_collect_plain_pairs(value, "", pairs)
|
|
67
81
|
pairs.sort(key=lambda p: p[0].encode("utf-16-be"))
|
|
@@ -84,6 +98,17 @@ def internal_redact_secrets(value: Any) -> None:
|
|
|
84
98
|
_redact_secrets(value)
|
|
85
99
|
|
|
86
100
|
|
|
101
|
+
def _apply_redaction_policy(value: Any, redaction_policy: RedactionPolicy) -> None:
|
|
102
|
+
if redaction_policy == RedactionPolicy.RedactionTraceOnly:
|
|
103
|
+
if isinstance(value, dict) and "trace" in value:
|
|
104
|
+
_redact_secrets(value["trace"])
|
|
105
|
+
return
|
|
106
|
+
if redaction_policy == RedactionPolicy.RedactionNone:
|
|
107
|
+
return
|
|
108
|
+
# Safety fallback for unknown values.
|
|
109
|
+
_redact_secrets(value)
|
|
110
|
+
|
|
111
|
+
|
|
87
112
|
def parse_size(s: str) -> int | None:
|
|
88
113
|
"""Parse a human-readable size string into bytes.
|
|
89
114
|
|
|
@@ -126,6 +151,43 @@ def parse_size(s: str) -> int | None:
|
|
|
126
151
|
# ═══════════════════════════════════════════
|
|
127
152
|
|
|
128
153
|
|
|
154
|
+
def _sanitize_for_json(value: Any, stack: set[int] | None = None) -> Any:
|
|
155
|
+
if stack is None:
|
|
156
|
+
stack = set()
|
|
157
|
+
|
|
158
|
+
if value is None or isinstance(value, (str, bool, int)):
|
|
159
|
+
return value
|
|
160
|
+
if isinstance(value, float):
|
|
161
|
+
if math.isfinite(value):
|
|
162
|
+
return value
|
|
163
|
+
return "<unsupported:float>"
|
|
164
|
+
if isinstance(value, BaseException):
|
|
165
|
+
return str(value)
|
|
166
|
+
|
|
167
|
+
if isinstance(value, dict):
|
|
168
|
+
obj_id = id(value)
|
|
169
|
+
if obj_id in stack:
|
|
170
|
+
return "<unsupported:circular>"
|
|
171
|
+
stack.add(obj_id)
|
|
172
|
+
out: dict[str, Any] = {}
|
|
173
|
+
for k, v in value.items():
|
|
174
|
+
key = k if isinstance(k, str) else str(k)
|
|
175
|
+
out[key] = _sanitize_for_json(v, stack)
|
|
176
|
+
stack.remove(obj_id)
|
|
177
|
+
return out
|
|
178
|
+
|
|
179
|
+
if isinstance(value, (list, tuple)):
|
|
180
|
+
obj_id = id(value)
|
|
181
|
+
if obj_id in stack:
|
|
182
|
+
return "<unsupported:circular>"
|
|
183
|
+
stack.add(obj_id)
|
|
184
|
+
out = [_sanitize_for_json(item, stack) for item in value]
|
|
185
|
+
stack.remove(obj_id)
|
|
186
|
+
return out
|
|
187
|
+
|
|
188
|
+
return f"<unsupported:{type(value).__name__}>"
|
|
189
|
+
|
|
190
|
+
|
|
129
191
|
def _redact_secrets(value: Any) -> None:
|
|
130
192
|
if isinstance(value, dict):
|
|
131
193
|
for k in list(value.keys()):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-first-data
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates for AI agents
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Repository, https://github.com/cmnspore/agent-first-data
|
|
@@ -74,11 +74,11 @@ Plain: args.input_path=/data/backup.tar.gz code=log event=startup config.max_fil
|
|
|
74
74
|
|
|
75
75
|
## API Reference
|
|
76
76
|
|
|
77
|
-
Total: **
|
|
77
|
+
Total: **13 public APIs and 2 types** + **AFDATA logging** (3 protocol builders + 4 output functions + 1 internal + 1 utility + 4 CLI helpers + `OutputFormat` + `RedactionPolicy`)
|
|
78
78
|
|
|
79
79
|
### Protocol Builders (returns dict)
|
|
80
80
|
|
|
81
|
-
Build AFDATA protocol structures. Return dict objects for
|
|
81
|
+
Build AFDATA protocol structures. Return dict objects for transport payloads.
|
|
82
82
|
|
|
83
83
|
```python
|
|
84
84
|
# Success (result)
|
|
@@ -91,7 +91,7 @@ build_json_error(message: str, trace: Any = None) -> dict
|
|
|
91
91
|
build_json(code: str, fields: Any, trace: Any = None) -> dict
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
**Use case:**
|
|
94
|
+
**Use case:** structured protocol payloads (frameworks automatically serialize)
|
|
95
95
|
|
|
96
96
|
**Example:**
|
|
97
97
|
```python
|
|
@@ -128,14 +128,21 @@ not_found = build_json(
|
|
|
128
128
|
|
|
129
129
|
### CLI/Log Output (returns str)
|
|
130
130
|
|
|
131
|
-
Format values for CLI output and logs.
|
|
131
|
+
Format values for CLI output and logs. `output_json` uses full `_secret` redaction by default. `output_json_with` supports explicit scoped policies. YAML and Plain always redact `_secret` and apply human-readable formatting.
|
|
132
132
|
|
|
133
133
|
```python
|
|
134
134
|
output_json(value: Any) -> str # Single-line JSON, original keys, for programs/logs
|
|
135
|
+
output_json_with(value: Any, redaction_policy: RedactionPolicy) -> str
|
|
135
136
|
output_yaml(value: Any) -> str # Multi-line YAML, keys stripped, values formatted
|
|
136
137
|
output_plain(value: Any) -> str # Single-line logfmt, keys stripped, values formatted
|
|
137
138
|
```
|
|
138
139
|
|
|
140
|
+
```python
|
|
141
|
+
class RedactionPolicy(enum.Enum):
|
|
142
|
+
RedactionTraceOnly = "RedactionTraceOnly"
|
|
143
|
+
RedactionNone = "RedactionNone"
|
|
144
|
+
```
|
|
145
|
+
|
|
139
146
|
**Example:**
|
|
140
147
|
```python
|
|
141
148
|
from agent_first_data import *
|
|
@@ -112,6 +112,12 @@ class TestCodeOverride:
|
|
|
112
112
|
assert m["code"] == "log"
|
|
113
113
|
assert m["event"] == "startup"
|
|
114
114
|
|
|
115
|
+
def test_exception_field_is_readable(self):
|
|
116
|
+
logger = make_logger("test_exc")
|
|
117
|
+
adapter = get_logger("test_exc")
|
|
118
|
+
m = capture_log(lambda: adapter.error("request failed", extra={"error": Exception("timeout")}))
|
|
119
|
+
assert m["error"] == "timeout"
|
|
120
|
+
|
|
115
121
|
|
|
116
122
|
class TestGetLogger:
|
|
117
123
|
def test_default_fields(self):
|
|
@@ -7,7 +7,10 @@ from agent_first_data import (
|
|
|
7
7
|
build_json_ok,
|
|
8
8
|
build_json_error,
|
|
9
9
|
build_json,
|
|
10
|
+
RedactionPolicy,
|
|
10
11
|
internal_redact_secrets,
|
|
12
|
+
output_json,
|
|
13
|
+
output_json_with,
|
|
11
14
|
)
|
|
12
15
|
from agent_first_data.format import (
|
|
13
16
|
_format_bytes_human,
|
|
@@ -83,3 +86,52 @@ def test_helper_fixtures():
|
|
|
83
86
|
elif name == "parse_size":
|
|
84
87
|
got = parse_size(inp)
|
|
85
88
|
assert got == expected, f"[helpers/{name}({inp!r})] got {got!r}"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_output_json_exception_field_is_readable():
|
|
92
|
+
out = output_json({"error": Exception("timeout")})
|
|
93
|
+
parsed = json.loads(out)
|
|
94
|
+
assert parsed["error"] == "timeout"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_output_json_unsupported_value_does_not_leak_secret():
|
|
98
|
+
class SecretRepr:
|
|
99
|
+
def __repr__(self) -> str:
|
|
100
|
+
return "Secret(sk-live-123)"
|
|
101
|
+
|
|
102
|
+
out = output_json({"meta": SecretRepr(), "api_key_secret": "sk-live-123"})
|
|
103
|
+
assert "sk-live-123" not in out
|
|
104
|
+
parsed = json.loads(out)
|
|
105
|
+
assert parsed["api_key_secret"] == "***"
|
|
106
|
+
assert parsed["meta"].startswith("<unsupported:")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_output_json_circular_reference():
|
|
110
|
+
v = {}
|
|
111
|
+
v["self"] = v
|
|
112
|
+
out = output_json(v)
|
|
113
|
+
parsed = json.loads(out)
|
|
114
|
+
assert parsed["self"] == "<unsupported:circular>"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_output_json_with_trace_only_redacts_only_trace():
|
|
118
|
+
out = output_json_with(
|
|
119
|
+
{
|
|
120
|
+
"code": "ok",
|
|
121
|
+
"result": {"api_key_secret": "sk-live-123"},
|
|
122
|
+
"trace": {"request_secret": "top-secret"},
|
|
123
|
+
},
|
|
124
|
+
RedactionPolicy.RedactionTraceOnly,
|
|
125
|
+
)
|
|
126
|
+
parsed = json.loads(out)
|
|
127
|
+
assert parsed["trace"]["request_secret"] == "***"
|
|
128
|
+
assert parsed["result"]["api_key_secret"] == "sk-live-123"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_output_json_with_none_keeps_secrets():
|
|
132
|
+
out = output_json_with(
|
|
133
|
+
{"api_key_secret": "sk-live-123"},
|
|
134
|
+
RedactionPolicy.RedactionNone,
|
|
135
|
+
)
|
|
136
|
+
parsed = json.loads(out)
|
|
137
|
+
assert parsed["api_key_secret"] == "sk-live-123"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_first_data-0.4.1 → agent_first_data-0.5.0}/agent_first_data.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|