agent-first-data 0.7.4__tar.gz → 0.8.1__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.7.4/agent_first_data.egg-info → agent_first_data-0.8.1}/PKG-INFO +27 -8
- agent_first_data-0.7.4/PKG-INFO → agent_first_data-0.8.1/README.md +24 -14
- {agent_first_data-0.7.4 → agent_first_data-0.8.1}/agent_first_data/__init__.py +16 -0
- {agent_first_data-0.7.4 → agent_first_data-0.8.1}/agent_first_data/format.py +138 -22
- agent_first_data-0.7.4/README.md → agent_first_data-0.8.1/agent_first_data.egg-info/PKG-INFO +33 -5
- {agent_first_data-0.7.4 → agent_first_data-0.8.1}/pyproject.toml +3 -3
- {agent_first_data-0.7.4 → agent_first_data-0.8.1}/tests/test_format.py +55 -0
- {agent_first_data-0.7.4 → agent_first_data-0.8.1}/agent_first_data/afdata_logging.py +0 -0
- {agent_first_data-0.7.4 → agent_first_data-0.8.1}/agent_first_data/cli.py +0 -0
- {agent_first_data-0.7.4 → agent_first_data-0.8.1}/agent_first_data.egg-info/SOURCES.txt +0 -0
- {agent_first_data-0.7.4 → agent_first_data-0.8.1}/agent_first_data.egg-info/dependency_links.txt +0 -0
- {agent_first_data-0.7.4 → agent_first_data-0.8.1}/agent_first_data.egg-info/top_level.txt +0 -0
- {agent_first_data-0.7.4 → agent_first_data-0.8.1}/setup.cfg +0 -0
- {agent_first_data-0.7.4 → agent_first_data-0.8.1}/tests/test_afdata_logging.py +0 -0
- {agent_first_data-0.7.4 → agent_first_data-0.8.1}/tests/test_cli.py +0 -0
- {agent_first_data-0.7.4 → agent_first_data-0.8.1}/tests/test_no_stderr_policy.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-first-data
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.8.1
|
|
4
|
+
Summary: A naming convention that lets AI agents understand your data without being told what it means.
|
|
5
5
|
License-Expression: MIT
|
|
6
|
-
Project-URL: Repository, https://github.com/
|
|
6
|
+
Project-URL: Repository, https://github.com/agentfirstkit/agent-first-data
|
|
7
7
|
Requires-Python: >=3.9
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
|
|
@@ -74,7 +74,7 @@ Plain: args.input_path=/data/backup.tar.gz code=log event=startup config.max_fil
|
|
|
74
74
|
|
|
75
75
|
## API Reference
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
Public APIs are grouped by role: protocol builders, redaction helpers, output formatters, internal redaction tools, utility/CLI helpers, types, and AFDATA logging integration.
|
|
78
78
|
|
|
79
79
|
### Protocol Builders (returns dict)
|
|
80
80
|
|
|
@@ -91,6 +91,16 @@ build_json_error(message: str, hint: str = None, trace: Any = None) -> dict
|
|
|
91
91
|
build_json(code: str, fields: Any, trace: Any = None) -> dict
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
+
### Redaction Helpers (returns Any)
|
|
95
|
+
|
|
96
|
+
Use these before raw HTTP/MCP/SSE serializers that do not call `output_json`.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
redacted_value(value: Any) -> Any
|
|
100
|
+
redacted_value_with(value: Any, redaction_policy: RedactionPolicy) -> Any
|
|
101
|
+
redacted_value_with_options(value: Any, redaction_options: RedactionOptions) -> Any
|
|
102
|
+
```
|
|
103
|
+
|
|
94
104
|
**Use case:** structured protocol payloads (frameworks automatically serialize)
|
|
95
105
|
|
|
96
106
|
**Example:**
|
|
@@ -129,23 +139,31 @@ not_found = build_json(
|
|
|
129
139
|
)
|
|
130
140
|
```
|
|
131
141
|
|
|
132
|
-
###
|
|
142
|
+
### Output Formatters (returns str)
|
|
133
143
|
|
|
134
|
-
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
|
|
144
|
+
Format values for CLI output and logs. `output_json` uses full `_secret` redaction by default. `output_json_with` supports explicit scoped policies. Use the `*_with_options` functions to pass legacy secret names such as `api_key`. YAML and Plain always redact secrets and apply human-readable formatting.
|
|
135
145
|
|
|
136
146
|
```python
|
|
137
147
|
output_json(value: Any) -> str # Single-line JSON, original keys, for programs/logs
|
|
138
148
|
output_json_with(value: Any, redaction_policy: RedactionPolicy) -> str
|
|
149
|
+
output_json_with_options(value: Any, redaction_options: RedactionOptions) -> str
|
|
139
150
|
output_yaml(value: Any) -> str # Multi-line YAML, keys stripped, values formatted
|
|
151
|
+
output_yaml_with_options(value: Any, redaction_options: RedactionOptions) -> str
|
|
140
152
|
output_plain(value: Any) -> str # Single-line logfmt, keys stripped, values formatted
|
|
153
|
+
output_plain_with_options(value: Any, redaction_options: RedactionOptions) -> str
|
|
141
154
|
```
|
|
142
155
|
|
|
143
156
|
```python
|
|
144
157
|
class RedactionPolicy(enum.Enum):
|
|
145
158
|
RedactionTraceOnly = "RedactionTraceOnly"
|
|
146
159
|
RedactionNone = "RedactionNone"
|
|
160
|
+
RedactionStrict = "RedactionStrict"
|
|
161
|
+
|
|
162
|
+
RedactionOptions(policy: RedactionPolicy | None = None, secret_names: Sequence[str] = ())
|
|
147
163
|
```
|
|
148
164
|
|
|
165
|
+
Secret names match exact field names at any nesting level; there is no trim, case folding, hyphen/underscore normalization, glob, regex, or substring matching. They do not change YAML/Plain suffix stripping.
|
|
166
|
+
|
|
149
167
|
**Example:**
|
|
150
168
|
```python
|
|
151
169
|
from agent_first_data import *
|
|
@@ -178,6 +196,7 @@ print(output_plain(data))
|
|
|
178
196
|
|
|
179
197
|
```python
|
|
180
198
|
internal_redact_secrets(value: Any) -> None # Manually redact secrets in-place
|
|
199
|
+
internal_redact_secrets_with_options(value: Any, redaction_options: RedactionOptions) -> None
|
|
181
200
|
```
|
|
182
201
|
|
|
183
202
|
Most users don't need this. Output functions automatically protect secrets.
|
|
@@ -513,7 +532,7 @@ All formats automatically redact `_secret` fields.
|
|
|
513
532
|
|
|
514
533
|
## Repository
|
|
515
534
|
|
|
516
|
-
This package is part of the [agent-first-data](https://github.com/
|
|
535
|
+
This package is part of the [agent-first-data](https://github.com/agentfirstkit/agent-first-data) repository, which also contains:
|
|
517
536
|
|
|
518
537
|
- **`spec/`** — Full AFDATA specification with suffix definitions, protocol format rules, and cross-language test fixtures
|
|
519
538
|
- **`skills/`** — AI coding agent skill for working with AFDATA conventions
|
|
@@ -521,7 +540,7 @@ This package is part of the [agent-first-data](https://github.com/cmnspore/agent
|
|
|
521
540
|
To run tests, clone the full repository (tests use shared cross-language fixtures from `spec/fixtures/`):
|
|
522
541
|
|
|
523
542
|
```bash
|
|
524
|
-
git clone https://github.com/
|
|
543
|
+
git clone https://github.com/agentfirstkit/agent-first-data
|
|
525
544
|
cd agent-first-data/python
|
|
526
545
|
python -m pytest
|
|
527
546
|
```
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: agent-first-data
|
|
3
|
-
Version: 0.7.4
|
|
4
|
-
Summary: Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates for AI agents
|
|
5
|
-
License-Expression: MIT
|
|
6
|
-
Project-URL: Repository, https://github.com/cmnspore/agent-first-data
|
|
7
|
-
Requires-Python: >=3.9
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
|
|
10
1
|
# agent-first-data
|
|
11
2
|
|
|
12
3
|
**Agent-First Data (AFDATA)** — Suffix-driven output formatting and protocol templates for AI agents.
|
|
@@ -74,7 +65,7 @@ Plain: args.input_path=/data/backup.tar.gz code=log event=startup config.max_fil
|
|
|
74
65
|
|
|
75
66
|
## API Reference
|
|
76
67
|
|
|
77
|
-
|
|
68
|
+
Public APIs are grouped by role: protocol builders, redaction helpers, output formatters, internal redaction tools, utility/CLI helpers, types, and AFDATA logging integration.
|
|
78
69
|
|
|
79
70
|
### Protocol Builders (returns dict)
|
|
80
71
|
|
|
@@ -91,6 +82,16 @@ build_json_error(message: str, hint: str = None, trace: Any = None) -> dict
|
|
|
91
82
|
build_json(code: str, fields: Any, trace: Any = None) -> dict
|
|
92
83
|
```
|
|
93
84
|
|
|
85
|
+
### Redaction Helpers (returns Any)
|
|
86
|
+
|
|
87
|
+
Use these before raw HTTP/MCP/SSE serializers that do not call `output_json`.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
redacted_value(value: Any) -> Any
|
|
91
|
+
redacted_value_with(value: Any, redaction_policy: RedactionPolicy) -> Any
|
|
92
|
+
redacted_value_with_options(value: Any, redaction_options: RedactionOptions) -> Any
|
|
93
|
+
```
|
|
94
|
+
|
|
94
95
|
**Use case:** structured protocol payloads (frameworks automatically serialize)
|
|
95
96
|
|
|
96
97
|
**Example:**
|
|
@@ -129,23 +130,31 @@ not_found = build_json(
|
|
|
129
130
|
)
|
|
130
131
|
```
|
|
131
132
|
|
|
132
|
-
###
|
|
133
|
+
### Output Formatters (returns str)
|
|
133
134
|
|
|
134
|
-
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
|
|
135
|
+
Format values for CLI output and logs. `output_json` uses full `_secret` redaction by default. `output_json_with` supports explicit scoped policies. Use the `*_with_options` functions to pass legacy secret names such as `api_key`. YAML and Plain always redact secrets and apply human-readable formatting.
|
|
135
136
|
|
|
136
137
|
```python
|
|
137
138
|
output_json(value: Any) -> str # Single-line JSON, original keys, for programs/logs
|
|
138
139
|
output_json_with(value: Any, redaction_policy: RedactionPolicy) -> str
|
|
140
|
+
output_json_with_options(value: Any, redaction_options: RedactionOptions) -> str
|
|
139
141
|
output_yaml(value: Any) -> str # Multi-line YAML, keys stripped, values formatted
|
|
142
|
+
output_yaml_with_options(value: Any, redaction_options: RedactionOptions) -> str
|
|
140
143
|
output_plain(value: Any) -> str # Single-line logfmt, keys stripped, values formatted
|
|
144
|
+
output_plain_with_options(value: Any, redaction_options: RedactionOptions) -> str
|
|
141
145
|
```
|
|
142
146
|
|
|
143
147
|
```python
|
|
144
148
|
class RedactionPolicy(enum.Enum):
|
|
145
149
|
RedactionTraceOnly = "RedactionTraceOnly"
|
|
146
150
|
RedactionNone = "RedactionNone"
|
|
151
|
+
RedactionStrict = "RedactionStrict"
|
|
152
|
+
|
|
153
|
+
RedactionOptions(policy: RedactionPolicy | None = None, secret_names: Sequence[str] = ())
|
|
147
154
|
```
|
|
148
155
|
|
|
156
|
+
Secret names match exact field names at any nesting level; there is no trim, case folding, hyphen/underscore normalization, glob, regex, or substring matching. They do not change YAML/Plain suffix stripping.
|
|
157
|
+
|
|
149
158
|
**Example:**
|
|
150
159
|
```python
|
|
151
160
|
from agent_first_data import *
|
|
@@ -178,6 +187,7 @@ print(output_plain(data))
|
|
|
178
187
|
|
|
179
188
|
```python
|
|
180
189
|
internal_redact_secrets(value: Any) -> None # Manually redact secrets in-place
|
|
190
|
+
internal_redact_secrets_with_options(value: Any, redaction_options: RedactionOptions) -> None
|
|
181
191
|
```
|
|
182
192
|
|
|
183
193
|
Most users don't need this. Output functions automatically protect secrets.
|
|
@@ -513,7 +523,7 @@ All formats automatically redact `_secret` fields.
|
|
|
513
523
|
|
|
514
524
|
## Repository
|
|
515
525
|
|
|
516
|
-
This package is part of the [agent-first-data](https://github.com/
|
|
526
|
+
This package is part of the [agent-first-data](https://github.com/agentfirstkit/agent-first-data) repository, which also contains:
|
|
517
527
|
|
|
518
528
|
- **`spec/`** — Full AFDATA specification with suffix definitions, protocol format rules, and cross-language test fixtures
|
|
519
529
|
- **`skills/`** — AI coding agent skill for working with AFDATA conventions
|
|
@@ -521,7 +531,7 @@ This package is part of the [agent-first-data](https://github.com/cmnspore/agent
|
|
|
521
531
|
To run tests, clone the full repository (tests use shared cross-language fixtures from `spec/fixtures/`):
|
|
522
532
|
|
|
523
533
|
```bash
|
|
524
|
-
git clone https://github.com/
|
|
534
|
+
git clone https://github.com/agentfirstkit/agent-first-data
|
|
525
535
|
cd agent-first-data/python
|
|
526
536
|
python -m pytest
|
|
527
537
|
```
|
|
@@ -5,11 +5,19 @@ from agent_first_data.format import (
|
|
|
5
5
|
build_json_error,
|
|
6
6
|
build_json,
|
|
7
7
|
RedactionPolicy,
|
|
8
|
+
RedactionOptions,
|
|
8
9
|
output_json,
|
|
9
10
|
output_json_with,
|
|
11
|
+
output_json_with_options,
|
|
10
12
|
output_yaml,
|
|
13
|
+
output_yaml_with_options,
|
|
11
14
|
output_plain,
|
|
15
|
+
output_plain_with_options,
|
|
12
16
|
internal_redact_secrets,
|
|
17
|
+
internal_redact_secrets_with_options,
|
|
18
|
+
redacted_value,
|
|
19
|
+
redacted_value_with,
|
|
20
|
+
redacted_value_with_options,
|
|
13
21
|
parse_size,
|
|
14
22
|
)
|
|
15
23
|
|
|
@@ -36,11 +44,19 @@ __all__ = [
|
|
|
36
44
|
"build_json_error",
|
|
37
45
|
"build_json",
|
|
38
46
|
"RedactionPolicy",
|
|
47
|
+
"RedactionOptions",
|
|
39
48
|
"output_json",
|
|
40
49
|
"output_json_with",
|
|
50
|
+
"output_json_with_options",
|
|
41
51
|
"output_yaml",
|
|
52
|
+
"output_yaml_with_options",
|
|
42
53
|
"output_plain",
|
|
54
|
+
"output_plain_with_options",
|
|
43
55
|
"internal_redact_secrets",
|
|
56
|
+
"internal_redact_secrets_with_options",
|
|
57
|
+
"redacted_value",
|
|
58
|
+
"redacted_value_with",
|
|
59
|
+
"redacted_value_with_options",
|
|
44
60
|
"parse_size",
|
|
45
61
|
"AfdataHandler",
|
|
46
62
|
"AfdataJsonHandler",
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
"""AFDATA output formatting and protocol templates.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
16 public APIs and 2 types: protocol builders, redacted value helpers,
|
|
4
|
+
output formatters, redaction, parse_size, RedactionPolicy, and RedactionOptions.
|
|
4
5
|
"""
|
|
5
6
|
|
|
6
7
|
from __future__ import annotations
|
|
7
8
|
|
|
8
9
|
import json
|
|
9
10
|
import math
|
|
11
|
+
from dataclasses import dataclass
|
|
10
12
|
from datetime import datetime, timezone
|
|
11
13
|
from enum import Enum
|
|
12
|
-
from typing import Any
|
|
14
|
+
from typing import Any, Sequence
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
# ═══════════════════════════════════════════
|
|
@@ -51,25 +53,48 @@ def build_json(code: str, fields: Any, trace: Any = None) -> dict:
|
|
|
51
53
|
class RedactionPolicy(str, Enum):
|
|
52
54
|
RedactionTraceOnly = "RedactionTraceOnly"
|
|
53
55
|
RedactionNone = "RedactionNone"
|
|
56
|
+
RedactionStrict = "RedactionStrict"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True)
|
|
60
|
+
class RedactionOptions:
|
|
61
|
+
"""Redaction options for legacy secret field names."""
|
|
62
|
+
|
|
63
|
+
policy: RedactionPolicy | None = None
|
|
64
|
+
# Exact field-name matches at any nesting level.
|
|
65
|
+
secret_names: Sequence[str] = ()
|
|
54
66
|
|
|
55
67
|
|
|
56
68
|
def output_json(value: Any) -> str:
|
|
57
69
|
"""Format as single-line JSON. Secrets redacted, original keys, raw values."""
|
|
58
|
-
|
|
59
|
-
_redact_secrets(v)
|
|
60
|
-
return json.dumps(v, ensure_ascii=False, separators=(",", ":"))
|
|
70
|
+
return json.dumps(redacted_value(value), ensure_ascii=False, separators=(",", ":"))
|
|
61
71
|
|
|
62
72
|
|
|
63
73
|
def output_json_with(value: Any, redaction_policy: RedactionPolicy) -> str:
|
|
64
74
|
"""Format as single-line JSON with explicit redaction policy."""
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
return json.dumps(redacted_value_with(value, redaction_policy), ensure_ascii=False, separators=(",", ":"))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def output_json_with_options(value: Any, redaction_options: RedactionOptions) -> str:
|
|
79
|
+
"""Format as single-line JSON with explicit redaction options."""
|
|
80
|
+
return json.dumps(
|
|
81
|
+
redacted_value_with_options(value, redaction_options),
|
|
82
|
+
ensure_ascii=False,
|
|
83
|
+
separators=(",", ":"),
|
|
84
|
+
)
|
|
68
85
|
|
|
69
86
|
|
|
70
87
|
def output_yaml(value: Any) -> str:
|
|
71
88
|
"""Format as multi-line YAML. Keys stripped, values formatted, secrets redacted."""
|
|
72
|
-
value =
|
|
89
|
+
value = redacted_value(value)
|
|
90
|
+
lines = ["---"]
|
|
91
|
+
_render_yaml_processed(value, 0, lines)
|
|
92
|
+
return "\n".join(lines)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def output_yaml_with_options(value: Any, redaction_options: RedactionOptions) -> str:
|
|
96
|
+
"""Format as multi-line YAML with explicit redaction options."""
|
|
97
|
+
value = redacted_value_with_options(value, redaction_options)
|
|
73
98
|
lines = ["---"]
|
|
74
99
|
_render_yaml_processed(value, 0, lines)
|
|
75
100
|
return "\n".join(lines)
|
|
@@ -77,16 +102,25 @@ def output_yaml(value: Any) -> str:
|
|
|
77
102
|
|
|
78
103
|
def output_plain(value: Any) -> str:
|
|
79
104
|
"""Format as single-line logfmt. Keys stripped, values formatted, secrets redacted."""
|
|
80
|
-
value =
|
|
105
|
+
value = redacted_value(value)
|
|
81
106
|
pairs: list[tuple[str, str]] = []
|
|
82
107
|
_collect_plain_pairs(value, "", pairs)
|
|
83
108
|
pairs.sort(key=lambda p: p[0].encode("utf-16-be"))
|
|
84
109
|
parts = []
|
|
85
110
|
for k, v in pairs:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
111
|
+
parts.append(f"{k}={_quote_logfmt_value(v)}")
|
|
112
|
+
return " ".join(parts)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def output_plain_with_options(value: Any, redaction_options: RedactionOptions) -> str:
|
|
116
|
+
"""Format as single-line logfmt with explicit redaction options."""
|
|
117
|
+
value = redacted_value_with_options(value, redaction_options)
|
|
118
|
+
pairs: list[tuple[str, str]] = []
|
|
119
|
+
_collect_plain_pairs(value, "", pairs)
|
|
120
|
+
pairs.sort(key=lambda p: p[0].encode("utf-16-be"))
|
|
121
|
+
parts = []
|
|
122
|
+
for k, v in pairs:
|
|
123
|
+
parts.append(f"{k}={_quote_logfmt_value(v)}")
|
|
90
124
|
return " ".join(parts)
|
|
91
125
|
|
|
92
126
|
|
|
@@ -100,15 +134,57 @@ def internal_redact_secrets(value: Any) -> None:
|
|
|
100
134
|
_redact_secrets(value)
|
|
101
135
|
|
|
102
136
|
|
|
137
|
+
def internal_redact_secrets_with_options(value: Any, redaction_options: RedactionOptions) -> None:
|
|
138
|
+
"""Redact secret fields in-place using explicit redaction options."""
|
|
139
|
+
_apply_redaction_options(value, redaction_options)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def redacted_value(value: Any) -> Any:
|
|
143
|
+
"""Return a JSON-safe copy with default _secret redaction applied."""
|
|
144
|
+
v = _sanitize_for_json(value)
|
|
145
|
+
_redact_secrets(v)
|
|
146
|
+
return v
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def redacted_value_with(value: Any, redaction_policy: RedactionPolicy) -> Any:
|
|
150
|
+
"""Return a JSON-safe copy with an explicit redaction policy applied."""
|
|
151
|
+
v = _sanitize_for_json(value)
|
|
152
|
+
_apply_redaction_policy(v, redaction_policy)
|
|
153
|
+
return v
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def redacted_value_with_options(value: Any, redaction_options: RedactionOptions) -> Any:
|
|
157
|
+
"""Return a JSON-safe copy with explicit redaction options applied."""
|
|
158
|
+
v = _sanitize_for_json(value)
|
|
159
|
+
_apply_redaction_options(v, redaction_options)
|
|
160
|
+
return v
|
|
161
|
+
|
|
162
|
+
|
|
103
163
|
def _apply_redaction_policy(value: Any, redaction_policy: RedactionPolicy) -> None:
|
|
164
|
+
_apply_redaction_policy_with_names(value, redaction_policy, frozenset())
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _apply_redaction_options(value: Any, redaction_options: RedactionOptions) -> None:
|
|
168
|
+
secret_names = _secret_name_set(redaction_options.secret_names)
|
|
169
|
+
_apply_redaction_policy_with_names(value, redaction_options.policy, secret_names)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _apply_redaction_policy_with_names(
|
|
173
|
+
value: Any,
|
|
174
|
+
redaction_policy: RedactionPolicy | None,
|
|
175
|
+
secret_names: frozenset[str],
|
|
176
|
+
) -> None:
|
|
104
177
|
if redaction_policy == RedactionPolicy.RedactionTraceOnly:
|
|
105
178
|
if isinstance(value, dict) and "trace" in value:
|
|
106
|
-
_redact_secrets(value["trace"])
|
|
179
|
+
_redact_secrets(value["trace"], secret_names)
|
|
107
180
|
return
|
|
108
181
|
if redaction_policy == RedactionPolicy.RedactionNone:
|
|
109
182
|
return
|
|
110
|
-
|
|
111
|
-
|
|
183
|
+
if redaction_policy == RedactionPolicy.RedactionStrict:
|
|
184
|
+
_redact_secrets_strict(value, secret_names)
|
|
185
|
+
return
|
|
186
|
+
# Empty/unknown policy falls back to default full redaction.
|
|
187
|
+
_redact_secrets(value, secret_names)
|
|
112
188
|
|
|
113
189
|
|
|
114
190
|
def parse_size(s: str) -> int | None:
|
|
@@ -196,19 +272,43 @@ def _sanitize_for_json(value: Any, stack: set[int] | None = None) -> Any:
|
|
|
196
272
|
return f"<unsupported:{type(value).__name__}>"
|
|
197
273
|
|
|
198
274
|
|
|
199
|
-
def
|
|
275
|
+
def _secret_name_set(secret_names: Sequence[str]) -> frozenset[str]:
|
|
276
|
+
return frozenset(secret_names)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _key_has_secret_suffix(key: str) -> bool:
|
|
280
|
+
return key.endswith("_secret") or key.endswith("_SECRET")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _is_secret_key(key: str, secret_names: frozenset[str]) -> bool:
|
|
284
|
+
return _key_has_secret_suffix(key) or key in secret_names
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _redact_secrets(value: Any, secret_names: frozenset[str] = frozenset()) -> None:
|
|
200
288
|
if isinstance(value, dict):
|
|
201
289
|
for k in list(value.keys()):
|
|
202
|
-
if k
|
|
290
|
+
if _is_secret_key(k, secret_names):
|
|
203
291
|
if isinstance(value[k], (dict, list)):
|
|
204
|
-
_redact_secrets(value[k])
|
|
292
|
+
_redact_secrets(value[k], secret_names)
|
|
205
293
|
else:
|
|
206
294
|
value[k] = "***"
|
|
207
295
|
else:
|
|
208
|
-
_redact_secrets(value[k])
|
|
296
|
+
_redact_secrets(value[k], secret_names)
|
|
297
|
+
elif isinstance(value, list):
|
|
298
|
+
for item in value:
|
|
299
|
+
_redact_secrets(item, secret_names)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _redact_secrets_strict(value: Any, secret_names: frozenset[str] = frozenset()) -> None:
|
|
303
|
+
if isinstance(value, dict):
|
|
304
|
+
for k in list(value.keys()):
|
|
305
|
+
if _is_secret_key(k, secret_names):
|
|
306
|
+
value[k] = "***"
|
|
307
|
+
else:
|
|
308
|
+
_redact_secrets_strict(value[k], secret_names)
|
|
209
309
|
elif isinstance(value, list):
|
|
210
310
|
for item in value:
|
|
211
|
-
|
|
311
|
+
_redact_secrets_strict(item, secret_names)
|
|
212
312
|
|
|
213
313
|
|
|
214
314
|
# ═══════════════════════════════════════════
|
|
@@ -572,3 +672,19 @@ def _plain_scalar(value: Any) -> str:
|
|
|
572
672
|
if isinstance(value, (int, float)):
|
|
573
673
|
return str(value)
|
|
574
674
|
return str(value)
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def _quote_logfmt_value(value: str) -> str:
|
|
678
|
+
if value == "":
|
|
679
|
+
return ""
|
|
680
|
+
needs_quote = any(c.isspace() or c in '="\\"' for c in value)
|
|
681
|
+
if not needs_quote:
|
|
682
|
+
return value
|
|
683
|
+
escaped = (
|
|
684
|
+
value.replace("\\", "\\\\")
|
|
685
|
+
.replace('"', '\\"')
|
|
686
|
+
.replace("\n", "\\n")
|
|
687
|
+
.replace("\r", "\\r")
|
|
688
|
+
.replace("\t", "\\t")
|
|
689
|
+
)
|
|
690
|
+
return f'"{escaped}"'
|
agent_first_data-0.7.4/README.md → agent_first_data-0.8.1/agent_first_data.egg-info/PKG-INFO
RENAMED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-first-data
|
|
3
|
+
Version: 0.8.1
|
|
4
|
+
Summary: A naming convention that lets AI agents understand your data without being told what it means.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Repository, https://github.com/agentfirstkit/agent-first-data
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
1
10
|
# agent-first-data
|
|
2
11
|
|
|
3
12
|
**Agent-First Data (AFDATA)** — Suffix-driven output formatting and protocol templates for AI agents.
|
|
@@ -65,7 +74,7 @@ Plain: args.input_path=/data/backup.tar.gz code=log event=startup config.max_fil
|
|
|
65
74
|
|
|
66
75
|
## API Reference
|
|
67
76
|
|
|
68
|
-
|
|
77
|
+
Public APIs are grouped by role: protocol builders, redaction helpers, output formatters, internal redaction tools, utility/CLI helpers, types, and AFDATA logging integration.
|
|
69
78
|
|
|
70
79
|
### Protocol Builders (returns dict)
|
|
71
80
|
|
|
@@ -82,6 +91,16 @@ build_json_error(message: str, hint: str = None, trace: Any = None) -> dict
|
|
|
82
91
|
build_json(code: str, fields: Any, trace: Any = None) -> dict
|
|
83
92
|
```
|
|
84
93
|
|
|
94
|
+
### Redaction Helpers (returns Any)
|
|
95
|
+
|
|
96
|
+
Use these before raw HTTP/MCP/SSE serializers that do not call `output_json`.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
redacted_value(value: Any) -> Any
|
|
100
|
+
redacted_value_with(value: Any, redaction_policy: RedactionPolicy) -> Any
|
|
101
|
+
redacted_value_with_options(value: Any, redaction_options: RedactionOptions) -> Any
|
|
102
|
+
```
|
|
103
|
+
|
|
85
104
|
**Use case:** structured protocol payloads (frameworks automatically serialize)
|
|
86
105
|
|
|
87
106
|
**Example:**
|
|
@@ -120,23 +139,31 @@ not_found = build_json(
|
|
|
120
139
|
)
|
|
121
140
|
```
|
|
122
141
|
|
|
123
|
-
###
|
|
142
|
+
### Output Formatters (returns str)
|
|
124
143
|
|
|
125
|
-
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
|
|
144
|
+
Format values for CLI output and logs. `output_json` uses full `_secret` redaction by default. `output_json_with` supports explicit scoped policies. Use the `*_with_options` functions to pass legacy secret names such as `api_key`. YAML and Plain always redact secrets and apply human-readable formatting.
|
|
126
145
|
|
|
127
146
|
```python
|
|
128
147
|
output_json(value: Any) -> str # Single-line JSON, original keys, for programs/logs
|
|
129
148
|
output_json_with(value: Any, redaction_policy: RedactionPolicy) -> str
|
|
149
|
+
output_json_with_options(value: Any, redaction_options: RedactionOptions) -> str
|
|
130
150
|
output_yaml(value: Any) -> str # Multi-line YAML, keys stripped, values formatted
|
|
151
|
+
output_yaml_with_options(value: Any, redaction_options: RedactionOptions) -> str
|
|
131
152
|
output_plain(value: Any) -> str # Single-line logfmt, keys stripped, values formatted
|
|
153
|
+
output_plain_with_options(value: Any, redaction_options: RedactionOptions) -> str
|
|
132
154
|
```
|
|
133
155
|
|
|
134
156
|
```python
|
|
135
157
|
class RedactionPolicy(enum.Enum):
|
|
136
158
|
RedactionTraceOnly = "RedactionTraceOnly"
|
|
137
159
|
RedactionNone = "RedactionNone"
|
|
160
|
+
RedactionStrict = "RedactionStrict"
|
|
161
|
+
|
|
162
|
+
RedactionOptions(policy: RedactionPolicy | None = None, secret_names: Sequence[str] = ())
|
|
138
163
|
```
|
|
139
164
|
|
|
165
|
+
Secret names match exact field names at any nesting level; there is no trim, case folding, hyphen/underscore normalization, glob, regex, or substring matching. They do not change YAML/Plain suffix stripping.
|
|
166
|
+
|
|
140
167
|
**Example:**
|
|
141
168
|
```python
|
|
142
169
|
from agent_first_data import *
|
|
@@ -169,6 +196,7 @@ print(output_plain(data))
|
|
|
169
196
|
|
|
170
197
|
```python
|
|
171
198
|
internal_redact_secrets(value: Any) -> None # Manually redact secrets in-place
|
|
199
|
+
internal_redact_secrets_with_options(value: Any, redaction_options: RedactionOptions) -> None
|
|
172
200
|
```
|
|
173
201
|
|
|
174
202
|
Most users don't need this. Output functions automatically protect secrets.
|
|
@@ -504,7 +532,7 @@ All formats automatically redact `_secret` fields.
|
|
|
504
532
|
|
|
505
533
|
## Repository
|
|
506
534
|
|
|
507
|
-
This package is part of the [agent-first-data](https://github.com/
|
|
535
|
+
This package is part of the [agent-first-data](https://github.com/agentfirstkit/agent-first-data) repository, which also contains:
|
|
508
536
|
|
|
509
537
|
- **`spec/`** — Full AFDATA specification with suffix definitions, protocol format rules, and cross-language test fixtures
|
|
510
538
|
- **`skills/`** — AI coding agent skill for working with AFDATA conventions
|
|
@@ -512,7 +540,7 @@ This package is part of the [agent-first-data](https://github.com/cmnspore/agent
|
|
|
512
540
|
To run tests, clone the full repository (tests use shared cross-language fixtures from `spec/fixtures/`):
|
|
513
541
|
|
|
514
542
|
```bash
|
|
515
|
-
git clone https://github.com/
|
|
543
|
+
git clone https://github.com/agentfirstkit/agent-first-data
|
|
516
544
|
cd agent-first-data/python
|
|
517
545
|
python -m pytest
|
|
518
546
|
```
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agent-first-data"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.8.1"
|
|
4
|
+
description = "A naming convention that lets AI agents understand your data without being told what it means."
|
|
5
5
|
license = "MIT"
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
requires-python = ">=3.9"
|
|
8
8
|
dependencies = []
|
|
9
9
|
|
|
10
10
|
[project.urls]
|
|
11
|
-
Repository = "https://github.com/
|
|
11
|
+
Repository = "https://github.com/agentfirstkit/agent-first-data"
|
|
12
12
|
|
|
13
13
|
[build-system]
|
|
14
14
|
requires = ["setuptools>=68"]
|
|
@@ -8,11 +8,19 @@ from agent_first_data import (
|
|
|
8
8
|
build_json_error,
|
|
9
9
|
build_json,
|
|
10
10
|
RedactionPolicy,
|
|
11
|
+
RedactionOptions,
|
|
11
12
|
internal_redact_secrets,
|
|
13
|
+
internal_redact_secrets_with_options,
|
|
14
|
+
redacted_value,
|
|
15
|
+
redacted_value_with,
|
|
16
|
+
redacted_value_with_options,
|
|
12
17
|
output_json,
|
|
13
18
|
output_json_with,
|
|
19
|
+
output_json_with_options,
|
|
14
20
|
output_yaml,
|
|
21
|
+
output_yaml_with_options,
|
|
15
22
|
output_plain,
|
|
23
|
+
output_plain_with_options,
|
|
16
24
|
)
|
|
17
25
|
from agent_first_data.format import (
|
|
18
26
|
_format_bytes_human,
|
|
@@ -29,6 +37,12 @@ def _load(name):
|
|
|
29
37
|
return json.load(f)
|
|
30
38
|
|
|
31
39
|
|
|
40
|
+
def _redaction_options(case):
|
|
41
|
+
opts = case.get("options", {})
|
|
42
|
+
policy = RedactionPolicy(opts["policy"]) if "policy" in opts else None
|
|
43
|
+
return RedactionOptions(policy=policy, secret_names=opts.get("secret_names", ()))
|
|
44
|
+
|
|
45
|
+
|
|
32
46
|
# --- Redact fixtures ---
|
|
33
47
|
|
|
34
48
|
|
|
@@ -40,6 +54,30 @@ def test_redact_fixtures():
|
|
|
40
54
|
assert inp == case["expected"], f"[redact/{name}] got {inp}"
|
|
41
55
|
|
|
42
56
|
|
|
57
|
+
def test_redaction_options_fixtures():
|
|
58
|
+
for case in _load("redaction_options.json"):
|
|
59
|
+
name = case["name"]
|
|
60
|
+
options = _redaction_options(case)
|
|
61
|
+
expected = case["expected"]
|
|
62
|
+
|
|
63
|
+
got = redacted_value_with_options(case["input"], options)
|
|
64
|
+
assert got == expected, f"[redaction_options/{name}] value mismatch: {got}"
|
|
65
|
+
|
|
66
|
+
inp = json.loads(json.dumps(case["input"]))
|
|
67
|
+
internal_redact_secrets_with_options(inp, options)
|
|
68
|
+
assert inp == expected, f"[redaction_options/{name}] in-place mismatch: {inp}"
|
|
69
|
+
|
|
70
|
+
got_json = json.loads(output_json_with_options(case["input"], options))
|
|
71
|
+
assert got_json == expected, f"[redaction_options/{name}] json mismatch: {got_json}"
|
|
72
|
+
|
|
73
|
+
if "expected_yaml" in case:
|
|
74
|
+
got_yaml = output_yaml_with_options(case["input"], options)
|
|
75
|
+
assert got_yaml == case["expected_yaml"], f"[redaction_options/{name}] yaml mismatch: {got_yaml!r}"
|
|
76
|
+
if "expected_plain" in case:
|
|
77
|
+
got_plain = output_plain_with_options(case["input"], options)
|
|
78
|
+
assert got_plain == case["expected_plain"], f"[redaction_options/{name}] plain mismatch: {got_plain!r}"
|
|
79
|
+
|
|
80
|
+
|
|
43
81
|
# --- Protocol fixtures ---
|
|
44
82
|
|
|
45
83
|
|
|
@@ -156,3 +194,20 @@ def test_output_json_with_none_keeps_secrets():
|
|
|
156
194
|
)
|
|
157
195
|
parsed = json.loads(out)
|
|
158
196
|
assert parsed["api_key_secret"] == "sk-live-123"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_redacted_value_returns_safe_copy():
|
|
200
|
+
inp = {"api_key_secret": "sk-live-123", "nested": {"token_secret": "tok"}}
|
|
201
|
+
got = redacted_value(inp)
|
|
202
|
+
assert got["api_key_secret"] == "***"
|
|
203
|
+
assert got["nested"]["token_secret"] == "***"
|
|
204
|
+
assert inp["api_key_secret"] == "sk-live-123"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def test_redacted_value_with_strict_redacts_secret_subtree():
|
|
208
|
+
inp = {"db_secret": {"password_secret": "real", "host": "localhost"}}
|
|
209
|
+
default = redacted_value(inp)
|
|
210
|
+
strict = redacted_value_with(inp, RedactionPolicy.RedactionStrict)
|
|
211
|
+
assert default["db_secret"]["password_secret"] == "***"
|
|
212
|
+
assert default["db_secret"]["host"] == "localhost"
|
|
213
|
+
assert strict["db_secret"] == "***"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_first_data-0.7.4 → agent_first_data-0.8.1}/agent_first_data.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|