agent-first-data 0.7.3__tar.gz → 0.8.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.
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-first-data
3
- Version: 0.7.3
3
+ Version: 0.8.0
4
4
  Summary: Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates for AI agents
5
5
  License-Expression: MIT
6
- Project-URL: Repository, https://github.com/cmnspore/agent-first-data
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
- Total: **13 public APIs and 2 types** + **AFDATA logging** (3 protocol builders + 4 output functions + 1 internal + 1 utility + 4 CLI helpers + `OutputFormat` + `RedactionPolicy`)
77
+ Total: **15 public APIs and 2 types** + **AFDATA logging** (3 protocol builders + 2 redacted value helpers + 4 output functions + 1 internal + 1 utility + 4 CLI helpers + `OutputFormat` + `RedactionPolicy`)
78
78
 
79
79
  ### Protocol Builders (returns dict)
80
80
 
@@ -91,6 +91,15 @@ 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
+ ### Redacted Values (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
+ ```
102
+
94
103
  **Use case:** structured protocol payloads (frameworks automatically serialize)
95
104
 
96
105
  **Example:**
@@ -144,6 +153,7 @@ output_plain(value: Any) -> str # Single-line logfmt, keys stripped, values for
144
153
  class RedactionPolicy(enum.Enum):
145
154
  RedactionTraceOnly = "RedactionTraceOnly"
146
155
  RedactionNone = "RedactionNone"
156
+ RedactionStrict = "RedactionStrict"
147
157
  ```
148
158
 
149
159
  **Example:**
@@ -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/cmnspore/agent-first-data) repository, which also contains:
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/cmnspore/agent-first-data
534
+ git clone https://github.com/agentfirstkit/agent-first-data
525
535
  cd agent-first-data/python
526
536
  python -m pytest
527
537
  ```
@@ -65,7 +65,7 @@ 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: **13 public APIs and 2 types** + **AFDATA logging** (3 protocol builders + 4 output functions + 1 internal + 1 utility + 4 CLI helpers + `OutputFormat` + `RedactionPolicy`)
68
+ Total: **15 public APIs and 2 types** + **AFDATA logging** (3 protocol builders + 2 redacted value helpers + 4 output functions + 1 internal + 1 utility + 4 CLI helpers + `OutputFormat` + `RedactionPolicy`)
69
69
 
70
70
  ### Protocol Builders (returns dict)
71
71
 
@@ -82,6 +82,15 @@ build_json_error(message: str, hint: str = None, trace: Any = None) -> dict
82
82
  build_json(code: str, fields: Any, trace: Any = None) -> dict
83
83
  ```
84
84
 
85
+ ### Redacted Values (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
+ ```
93
+
85
94
  **Use case:** structured protocol payloads (frameworks automatically serialize)
86
95
 
87
96
  **Example:**
@@ -135,6 +144,7 @@ output_plain(value: Any) -> str # Single-line logfmt, keys stripped, values for
135
144
  class RedactionPolicy(enum.Enum):
136
145
  RedactionTraceOnly = "RedactionTraceOnly"
137
146
  RedactionNone = "RedactionNone"
147
+ RedactionStrict = "RedactionStrict"
138
148
  ```
139
149
 
140
150
  **Example:**
@@ -504,7 +514,7 @@ All formats automatically redact `_secret` fields.
504
514
 
505
515
  ## Repository
506
516
 
507
- This package is part of the [agent-first-data](https://github.com/cmnspore/agent-first-data) repository, which also contains:
517
+ This package is part of the [agent-first-data](https://github.com/agentfirstkit/agent-first-data) repository, which also contains:
508
518
 
509
519
  - **`spec/`** — Full AFDATA specification with suffix definitions, protocol format rules, and cross-language test fixtures
510
520
  - **`skills/`** — AI coding agent skill for working with AFDATA conventions
@@ -512,7 +522,7 @@ This package is part of the [agent-first-data](https://github.com/cmnspore/agent
512
522
  To run tests, clone the full repository (tests use shared cross-language fixtures from `spec/fixtures/`):
513
523
 
514
524
  ```bash
515
- git clone https://github.com/cmnspore/agent-first-data
525
+ git clone https://github.com/agentfirstkit/agent-first-data
516
526
  cd agent-first-data/python
517
527
  python -m pytest
518
528
  ```
@@ -10,6 +10,8 @@ from agent_first_data.format import (
10
10
  output_yaml,
11
11
  output_plain,
12
12
  internal_redact_secrets,
13
+ redacted_value,
14
+ redacted_value_with,
13
15
  parse_size,
14
16
  )
15
17
 
@@ -41,6 +43,8 @@ __all__ = [
41
43
  "output_yaml",
42
44
  "output_plain",
43
45
  "internal_redact_secrets",
46
+ "redacted_value",
47
+ "redacted_value_with",
44
48
  "parse_size",
45
49
  "AfdataHandler",
46
50
  "AfdataJsonHandler",
@@ -1,6 +1,7 @@
1
1
  """AFDATA output formatting and protocol templates.
2
2
 
3
- 9 public APIs and 1 type: 3 protocol builders + 4 output formatters + 1 redaction + 1 utility + RedactionPolicy.
3
+ 11 public APIs and 1 type: protocol builders, redacted value helpers,
4
+ output formatters, redaction, parse_size, and RedactionPolicy.
4
5
  """
5
6
 
6
7
  from __future__ import annotations
@@ -51,25 +52,22 @@ def build_json(code: str, fields: Any, trace: Any = None) -> dict:
51
52
  class RedactionPolicy(str, Enum):
52
53
  RedactionTraceOnly = "RedactionTraceOnly"
53
54
  RedactionNone = "RedactionNone"
55
+ RedactionStrict = "RedactionStrict"
54
56
 
55
57
 
56
58
  def output_json(value: Any) -> str:
57
59
  """Format as single-line JSON. Secrets redacted, original keys, raw values."""
58
- v = _sanitize_for_json(value)
59
- _redact_secrets(v)
60
- return json.dumps(v, ensure_ascii=False, separators=(",", ":"))
60
+ return json.dumps(redacted_value(value), ensure_ascii=False, separators=(",", ":"))
61
61
 
62
62
 
63
63
  def output_json_with(value: Any, redaction_policy: RedactionPolicy) -> str:
64
64
  """Format as single-line JSON with explicit redaction policy."""
65
- v = _sanitize_for_json(value)
66
- _apply_redaction_policy(v, redaction_policy)
67
- return json.dumps(v, ensure_ascii=False, separators=(",", ":"))
65
+ return json.dumps(redacted_value_with(value, redaction_policy), ensure_ascii=False, separators=(",", ":"))
68
66
 
69
67
 
70
68
  def output_yaml(value: Any) -> str:
71
69
  """Format as multi-line YAML. Keys stripped, values formatted, secrets redacted."""
72
- value = _sanitize_for_json(value)
70
+ value = redacted_value(value)
73
71
  lines = ["---"]
74
72
  _render_yaml_processed(value, 0, lines)
75
73
  return "\n".join(lines)
@@ -77,16 +75,13 @@ def output_yaml(value: Any) -> str:
77
75
 
78
76
  def output_plain(value: Any) -> str:
79
77
  """Format as single-line logfmt. Keys stripped, values formatted, secrets redacted."""
80
- value = _sanitize_for_json(value)
78
+ value = redacted_value(value)
81
79
  pairs: list[tuple[str, str]] = []
82
80
  _collect_plain_pairs(value, "", pairs)
83
81
  pairs.sort(key=lambda p: p[0].encode("utf-16-be"))
84
82
  parts = []
85
83
  for k, v in pairs:
86
- if " " in v:
87
- parts.append(f'{k}="{v}"')
88
- else:
89
- parts.append(f"{k}={v}")
84
+ parts.append(f"{k}={_quote_logfmt_value(v)}")
90
85
  return " ".join(parts)
91
86
 
92
87
 
@@ -100,6 +95,20 @@ def internal_redact_secrets(value: Any) -> None:
100
95
  _redact_secrets(value)
101
96
 
102
97
 
98
+ def redacted_value(value: Any) -> Any:
99
+ """Return a JSON-safe copy with default _secret redaction applied."""
100
+ v = _sanitize_for_json(value)
101
+ _redact_secrets(v)
102
+ return v
103
+
104
+
105
+ def redacted_value_with(value: Any, redaction_policy: RedactionPolicy) -> Any:
106
+ """Return a JSON-safe copy with an explicit redaction policy applied."""
107
+ v = _sanitize_for_json(value)
108
+ _apply_redaction_policy(v, redaction_policy)
109
+ return v
110
+
111
+
103
112
  def _apply_redaction_policy(value: Any, redaction_policy: RedactionPolicy) -> None:
104
113
  if redaction_policy == RedactionPolicy.RedactionTraceOnly:
105
114
  if isinstance(value, dict) and "trace" in value:
@@ -107,6 +116,9 @@ def _apply_redaction_policy(value: Any, redaction_policy: RedactionPolicy) -> No
107
116
  return
108
117
  if redaction_policy == RedactionPolicy.RedactionNone:
109
118
  return
119
+ if redaction_policy == RedactionPolicy.RedactionStrict:
120
+ _redact_secrets_strict(value)
121
+ return
110
122
  # Safety fallback for unknown values.
111
123
  _redact_secrets(value)
112
124
 
@@ -211,6 +223,18 @@ def _redact_secrets(value: Any) -> None:
211
223
  _redact_secrets(item)
212
224
 
213
225
 
226
+ def _redact_secrets_strict(value: Any) -> None:
227
+ if isinstance(value, dict):
228
+ for k in list(value.keys()):
229
+ if k.endswith("_secret") or k.endswith("_SECRET"):
230
+ value[k] = "***"
231
+ else:
232
+ _redact_secrets_strict(value[k])
233
+ elif isinstance(value, list):
234
+ for item in value:
235
+ _redact_secrets_strict(item)
236
+
237
+
214
238
  # ═══════════════════════════════════════════
215
239
  # Suffix Processing
216
240
  # ═══════════════════════════════════════════
@@ -572,3 +596,19 @@ def _plain_scalar(value: Any) -> str:
572
596
  if isinstance(value, (int, float)):
573
597
  return str(value)
574
598
  return str(value)
599
+
600
+
601
+ def _quote_logfmt_value(value: str) -> str:
602
+ if value == "":
603
+ return ""
604
+ needs_quote = any(c.isspace() or c in '="\\"' for c in value)
605
+ if not needs_quote:
606
+ return value
607
+ escaped = (
608
+ value.replace("\\", "\\\\")
609
+ .replace('"', '\\"')
610
+ .replace("\n", "\\n")
611
+ .replace("\r", "\\r")
612
+ .replace("\t", "\\t")
613
+ )
614
+ return f'"{escaped}"'
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-first-data
3
- Version: 0.7.3
3
+ Version: 0.8.0
4
4
  Summary: Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates for AI agents
5
5
  License-Expression: MIT
6
- Project-URL: Repository, https://github.com/cmnspore/agent-first-data
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
- Total: **13 public APIs and 2 types** + **AFDATA logging** (3 protocol builders + 4 output functions + 1 internal + 1 utility + 4 CLI helpers + `OutputFormat` + `RedactionPolicy`)
77
+ Total: **15 public APIs and 2 types** + **AFDATA logging** (3 protocol builders + 2 redacted value helpers + 4 output functions + 1 internal + 1 utility + 4 CLI helpers + `OutputFormat` + `RedactionPolicy`)
78
78
 
79
79
  ### Protocol Builders (returns dict)
80
80
 
@@ -91,6 +91,15 @@ 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
+ ### Redacted Values (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
+ ```
102
+
94
103
  **Use case:** structured protocol payloads (frameworks automatically serialize)
95
104
 
96
105
  **Example:**
@@ -144,6 +153,7 @@ output_plain(value: Any) -> str # Single-line logfmt, keys stripped, values for
144
153
  class RedactionPolicy(enum.Enum):
145
154
  RedactionTraceOnly = "RedactionTraceOnly"
146
155
  RedactionNone = "RedactionNone"
156
+ RedactionStrict = "RedactionStrict"
147
157
  ```
148
158
 
149
159
  **Example:**
@@ -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/cmnspore/agent-first-data) repository, which also contains:
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/cmnspore/agent-first-data
534
+ git clone https://github.com/agentfirstkit/agent-first-data
525
535
  cd agent-first-data/python
526
536
  python -m pytest
527
537
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agent-first-data"
3
- version = "0.7.3"
3
+ version = "0.8.0"
4
4
  description = "Agent-First Data (AFDATA) — suffix-driven output formatting and protocol templates for AI agents"
5
5
  license = "MIT"
6
6
  readme = "README.md"
@@ -8,7 +8,7 @@ requires-python = ">=3.9"
8
8
  dependencies = []
9
9
 
10
10
  [project.urls]
11
- Repository = "https://github.com/cmnspore/agent-first-data"
11
+ Repository = "https://github.com/agentfirstkit/agent-first-data"
12
12
 
13
13
  [build-system]
14
14
  requires = ["setuptools>=68"]
@@ -9,6 +9,8 @@ from agent_first_data import (
9
9
  build_json,
10
10
  RedactionPolicy,
11
11
  internal_redact_secrets,
12
+ redacted_value,
13
+ redacted_value_with,
12
14
  output_json,
13
15
  output_json_with,
14
16
  output_yaml,
@@ -156,3 +158,20 @@ def test_output_json_with_none_keeps_secrets():
156
158
  )
157
159
  parsed = json.loads(out)
158
160
  assert parsed["api_key_secret"] == "sk-live-123"
161
+
162
+
163
+ def test_redacted_value_returns_safe_copy():
164
+ inp = {"api_key_secret": "sk-live-123", "nested": {"token_secret": "tok"}}
165
+ got = redacted_value(inp)
166
+ assert got["api_key_secret"] == "***"
167
+ assert got["nested"]["token_secret"] == "***"
168
+ assert inp["api_key_secret"] == "sk-live-123"
169
+
170
+
171
+ def test_redacted_value_with_strict_redacts_secret_subtree():
172
+ inp = {"db_secret": {"password_secret": "real", "host": "localhost"}}
173
+ default = redacted_value(inp)
174
+ strict = redacted_value_with(inp, RedactionPolicy.RedactionStrict)
175
+ assert default["db_secret"]["password_secret"] == "***"
176
+ assert default["db_secret"]["host"] == "localhost"
177
+ assert strict["db_secret"] == "***"