agent-first-data 0.16.0__tar.gz → 0.16.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.16.0 → agent_first_data-0.16.1}/PKG-INFO +1 -1
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/agent_first_data/assets/protocol-v1.schema.json +2 -19
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/agent_first_data/assets/skills/agent-first-data/references/rules.md +3 -3
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/agent_first_data/cli.py +4 -10
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/agent_first_data/format.py +18 -139
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/agent_first_data.egg-info/PKG-INFO +1 -1
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/pyproject.toml +1 -1
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/tests/test_cli.py +10 -3
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/tests/test_format.py +9 -12
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/README.md +0 -0
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/agent_first_data/__init__.py +0 -0
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/agent_first_data/assets/registry.json +0 -0
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/agent_first_data/assets/skills/agent-first-data/SKILL.md +0 -0
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/agent_first_data/assets/skills/agent-first-data/references/protocol-v1.schema.json +0 -0
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/agent_first_data/assets/skills/agent-first-data/references/registry.json +0 -0
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/agent_first_data/skill.py +0 -0
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/agent_first_data/stream_redirect.py +0 -0
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/agent_first_data.egg-info/SOURCES.txt +0 -0
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/agent_first_data.egg-info/dependency_links.txt +0 -0
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/agent_first_data.egg-info/top_level.txt +0 -0
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/setup.cfg +0 -0
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/tests/test_no_stderr_policy.py +0 -0
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/tests/test_skill.py +0 -0
- {agent_first_data-0.16.0 → agent_first_data-0.16.1}/tests/test_stream_redirect.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-first-data
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.1
|
|
4
4
|
Summary: A naming convention that lets AI agents understand your data without being told what it means.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Repository, https://github.com/agentfirstkit/agent-first-data
|
{agent_first_data-0.16.0 → agent_first_data-0.16.1}/agent_first_data/assets/protocol-v1.schema.json
RENAMED
|
@@ -47,13 +47,7 @@
|
|
|
47
47
|
"type": "object",
|
|
48
48
|
"properties": {
|
|
49
49
|
"kind": {"const": "progress"},
|
|
50
|
-
"progress":
|
|
51
|
-
"type": "object",
|
|
52
|
-
"properties": {
|
|
53
|
-
"message": {"type": "string", "minLength": 1}
|
|
54
|
-
},
|
|
55
|
-
"additionalProperties": true
|
|
56
|
-
},
|
|
50
|
+
"progress": true,
|
|
57
51
|
"trace": {"$ref": "#/$defs/trace"}
|
|
58
52
|
},
|
|
59
53
|
"required": ["kind", "progress"],
|
|
@@ -63,18 +57,7 @@
|
|
|
63
57
|
"type": "object",
|
|
64
58
|
"properties": {
|
|
65
59
|
"kind": {"const": "log"},
|
|
66
|
-
"log":
|
|
67
|
-
"type": "object",
|
|
68
|
-
"properties": {
|
|
69
|
-
"message": {"type": "string", "minLength": 1},
|
|
70
|
-
"level": {"enum": ["debug", "info", "warn", "error"]},
|
|
71
|
-
"code": false
|
|
72
|
-
},
|
|
73
|
-
"not": {
|
|
74
|
-
"required": ["code"]
|
|
75
|
-
},
|
|
76
|
-
"additionalProperties": true
|
|
77
|
-
},
|
|
60
|
+
"log": true,
|
|
78
61
|
"trace": {"$ref": "#/$defs/trace"}
|
|
79
62
|
},
|
|
80
63
|
"required": ["kind", "log"],
|
|
@@ -258,7 +258,7 @@ Startup payload fields are tool-defined; `config` is recommended, while `version
|
|
|
258
258
|
|
|
259
259
|
All use `kind` plus the same-named payload and optional `trace`. Do not split protocol events across `stdout` and `stderr`.
|
|
260
260
|
|
|
261
|
-
Base validators enforce mandatory envelope/error/lifecycle rules. Strict validators additionally require object-valued `trace` on every event
|
|
261
|
+
Base validators enforce mandatory envelope/error/lifecycle rules. Strict validators additionally require object-valued `trace` on every event and `retryable` on every error. Log and progress payloads remain tool-defined in both profiles. Use strict validation before claiming compliance with all recommendations.
|
|
262
262
|
|
|
263
263
|
---
|
|
264
264
|
|
|
@@ -298,11 +298,11 @@ let span = info_span!("request", request_id = %uuid);
|
|
|
298
298
|
let _guard = span.enter();
|
|
299
299
|
```
|
|
300
300
|
|
|
301
|
-
For Go, Python, and TypeScript: emit span fields explicitly in each log
|
|
301
|
+
For Go, Python, and TypeScript: emit span fields explicitly in each tool-defined log payload.
|
|
302
302
|
|
|
303
303
|
### Output fields
|
|
304
304
|
|
|
305
|
-
|
|
305
|
+
Log payloads have no required or reserved fields. Traditional logging adapters commonly add `message` and `level` (debug/info/warn/error), plus span and event fields, but those names and meanings are adapter conventions rather than AFDATA protocol requirements. Projects that need timestamps add `timestamp_epoch_ms` explicitly.
|
|
306
306
|
|
|
307
307
|
Log redaction is **by field name** (the same `_secret`/`_url` rule as all output), applied when the line is emitted. Name the secret field — `info!(api_key_secret = %key)` — rather than logging a whole object by its `Debug`/string rendering, which hides the inner field names from redaction. For structured/nested secret-bearing data, build a value, apply redaction through output formatters or redacted_value(), then emit via the standard formatting helpers — do not pass the struct to a `?`/`%`-rendered log field.
|
|
308
308
|
|
|
@@ -168,14 +168,8 @@ class CliEmitter:
|
|
|
168
168
|
# Apply log fields provider if this is a log event
|
|
169
169
|
if kind == "log" and self._log_fields_provider is not None:
|
|
170
170
|
provider_fields = self._log_fields_provider()
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
log_payload = envelope.get("log", {})
|
|
174
|
-
for reserved in ("message", "level", "code"):
|
|
175
|
-
if reserved in provider_fields:
|
|
176
|
-
raise RuntimeError(
|
|
177
|
-
f"log fields provider cannot write reserved field '{reserved}'"
|
|
178
|
-
)
|
|
171
|
+
log_payload = envelope.get("log")
|
|
172
|
+
if provider_fields and isinstance(log_payload, dict):
|
|
179
173
|
# Merge provider fields, explicit fields take precedence
|
|
180
174
|
merged_log = dict(provider_fields)
|
|
181
175
|
merged_log.update(log_payload)
|
|
@@ -211,7 +205,7 @@ class CliEmitter:
|
|
|
211
205
|
if not message or not isinstance(message, str):
|
|
212
206
|
raise ValueError("message must be a non-empty string")
|
|
213
207
|
# Use the builder to get default trace: {}
|
|
214
|
-
event = json_progress(message).build()
|
|
208
|
+
event = json_progress({"message": message}).build()
|
|
215
209
|
self.emit(event)
|
|
216
210
|
|
|
217
211
|
def emit_log(self, level: LogLevel | str, message: str) -> None:
|
|
@@ -220,7 +214,7 @@ class CliEmitter:
|
|
|
220
214
|
level = LogLevel(level)
|
|
221
215
|
if not message or not isinstance(message, str):
|
|
222
216
|
raise ValueError("message must be a non-empty string")
|
|
223
|
-
event = json_log(level, message).build()
|
|
217
|
+
event = json_log({"level": level.value, "message": message}).build()
|
|
224
218
|
self.emit(event)
|
|
225
219
|
|
|
226
220
|
|
|
@@ -183,50 +183,11 @@ class ErrorBuilder:
|
|
|
183
183
|
class ProgressBuilder:
|
|
184
184
|
"""Fluent builder for progress events."""
|
|
185
185
|
|
|
186
|
-
def __init__(self,
|
|
187
|
-
self.
|
|
186
|
+
def __init__(self, payload: Any) -> None:
|
|
187
|
+
self._payload = payload
|
|
188
188
|
self._trace: dict = {}
|
|
189
|
-
self._fields: dict[str, Any] = {}
|
|
190
189
|
self._errors: list[str] = []
|
|
191
190
|
|
|
192
|
-
def field(self, name: str, value: Any) -> ProgressBuilder:
|
|
193
|
-
"""Add a single extension field."""
|
|
194
|
-
if name == "message":
|
|
195
|
-
self._errors.append(f"cannot override reserved field '{name}'")
|
|
196
|
-
else:
|
|
197
|
-
self._fields[name] = value
|
|
198
|
-
return self
|
|
199
|
-
|
|
200
|
-
def fields(self, mapping: Mapping[str, Any]) -> ProgressBuilder:
|
|
201
|
-
"""Add multiple extension fields from a mapping."""
|
|
202
|
-
if not isinstance(mapping, Mapping):
|
|
203
|
-
self._errors.append("fields must be a mapping")
|
|
204
|
-
else:
|
|
205
|
-
for k, v in mapping.items():
|
|
206
|
-
if k == "message":
|
|
207
|
-
self._errors.append(f"cannot override reserved field '{k}'")
|
|
208
|
-
else:
|
|
209
|
-
self._fields[k] = v
|
|
210
|
-
return self
|
|
211
|
-
|
|
212
|
-
def extend(self, value: Any) -> ProgressBuilder:
|
|
213
|
-
"""Add extension fields from a dataclass or mapping."""
|
|
214
|
-
if hasattr(value, "__dataclass_fields__"):
|
|
215
|
-
from dataclasses import asdict
|
|
216
|
-
mapping = asdict(value)
|
|
217
|
-
elif isinstance(value, Mapping):
|
|
218
|
-
mapping = value
|
|
219
|
-
else:
|
|
220
|
-
self._errors.append("extend() requires a dataclass or mapping")
|
|
221
|
-
return self
|
|
222
|
-
|
|
223
|
-
for k, v in mapping.items():
|
|
224
|
-
if k == "message":
|
|
225
|
-
self._errors.append(f"cannot override reserved field '{k}'")
|
|
226
|
-
else:
|
|
227
|
-
self._fields[k] = v
|
|
228
|
-
return self
|
|
229
|
-
|
|
230
191
|
def trace(self, obj: Any) -> ProgressBuilder:
|
|
231
192
|
"""Set trace context."""
|
|
232
193
|
if not isinstance(obj, dict):
|
|
@@ -239,11 +200,9 @@ class ProgressBuilder:
|
|
|
239
200
|
"""Build and return the Event, raising EventBuildError if there are errors."""
|
|
240
201
|
if self._errors:
|
|
241
202
|
raise EventBuildError("; ".join(self._errors))
|
|
242
|
-
progress = {"message": self._message}
|
|
243
|
-
progress.update(self._fields)
|
|
244
203
|
envelope = {
|
|
245
204
|
"kind": "progress",
|
|
246
|
-
"progress":
|
|
205
|
+
"progress": self._payload,
|
|
247
206
|
"trace": self._trace,
|
|
248
207
|
}
|
|
249
208
|
return Event(envelope)
|
|
@@ -252,53 +211,11 @@ class ProgressBuilder:
|
|
|
252
211
|
class LogBuilder:
|
|
253
212
|
"""Fluent builder for log events."""
|
|
254
213
|
|
|
255
|
-
def __init__(self,
|
|
256
|
-
|
|
257
|
-
level = LogLevel(level)
|
|
258
|
-
self._level = level
|
|
259
|
-
self._message = message
|
|
214
|
+
def __init__(self, payload: Any) -> None:
|
|
215
|
+
self._payload = payload
|
|
260
216
|
self._trace: dict = {}
|
|
261
|
-
self._fields: dict[str, Any] = {}
|
|
262
217
|
self._errors: list[str] = []
|
|
263
218
|
|
|
264
|
-
def field(self, name: str, value: Any) -> LogBuilder:
|
|
265
|
-
"""Add a single extension field."""
|
|
266
|
-
if name in ("message", "level", "code"):
|
|
267
|
-
self._errors.append(f"cannot override reserved field '{name}'")
|
|
268
|
-
else:
|
|
269
|
-
self._fields[name] = value
|
|
270
|
-
return self
|
|
271
|
-
|
|
272
|
-
def fields(self, mapping: Mapping[str, Any]) -> LogBuilder:
|
|
273
|
-
"""Add multiple extension fields from a mapping."""
|
|
274
|
-
if not isinstance(mapping, Mapping):
|
|
275
|
-
self._errors.append("fields must be a mapping")
|
|
276
|
-
else:
|
|
277
|
-
for k, v in mapping.items():
|
|
278
|
-
if k in ("message", "level", "code"):
|
|
279
|
-
self._errors.append(f"cannot override reserved field '{k}'")
|
|
280
|
-
else:
|
|
281
|
-
self._fields[k] = v
|
|
282
|
-
return self
|
|
283
|
-
|
|
284
|
-
def extend(self, value: Any) -> LogBuilder:
|
|
285
|
-
"""Add extension fields from a dataclass or mapping."""
|
|
286
|
-
if hasattr(value, "__dataclass_fields__"):
|
|
287
|
-
from dataclasses import asdict
|
|
288
|
-
mapping = asdict(value)
|
|
289
|
-
elif isinstance(value, Mapping):
|
|
290
|
-
mapping = value
|
|
291
|
-
else:
|
|
292
|
-
self._errors.append("extend() requires a dataclass or mapping")
|
|
293
|
-
return self
|
|
294
|
-
|
|
295
|
-
for k, v in mapping.items():
|
|
296
|
-
if k in ("message", "level", "code"):
|
|
297
|
-
self._errors.append(f"cannot override reserved field '{k}'")
|
|
298
|
-
else:
|
|
299
|
-
self._fields[k] = v
|
|
300
|
-
return self
|
|
301
|
-
|
|
302
219
|
def trace(self, obj: Any) -> LogBuilder:
|
|
303
220
|
"""Set trace context."""
|
|
304
221
|
if not isinstance(obj, dict):
|
|
@@ -311,11 +228,9 @@ class LogBuilder:
|
|
|
311
228
|
"""Build and return the Event, raising EventBuildError if there are errors."""
|
|
312
229
|
if self._errors:
|
|
313
230
|
raise EventBuildError("; ".join(self._errors))
|
|
314
|
-
log = {"level": self._level.value, "message": self._message}
|
|
315
|
-
log.update(self._fields)
|
|
316
231
|
envelope = {
|
|
317
232
|
"kind": "log",
|
|
318
|
-
"log":
|
|
233
|
+
"log": self._payload,
|
|
319
234
|
"trace": self._trace,
|
|
320
235
|
}
|
|
321
236
|
return Event(envelope)
|
|
@@ -331,23 +246,22 @@ def json_error(code: str, message: str) -> ErrorBuilder:
|
|
|
331
246
|
return ErrorBuilder(code, message)
|
|
332
247
|
|
|
333
248
|
|
|
334
|
-
def json_progress(
|
|
249
|
+
def json_progress(payload: Any) -> ProgressBuilder:
|
|
335
250
|
"""Create a fluent progress builder."""
|
|
336
|
-
return ProgressBuilder(
|
|
251
|
+
return ProgressBuilder(payload)
|
|
337
252
|
|
|
338
253
|
|
|
339
|
-
def json_log(
|
|
254
|
+
def json_log(payload: Any) -> LogBuilder:
|
|
340
255
|
"""Create a fluent log builder."""
|
|
341
|
-
return LogBuilder(
|
|
256
|
+
return LogBuilder(payload)
|
|
342
257
|
|
|
343
258
|
|
|
344
259
|
def validate_protocol_event(event: Any, *, strict: bool = True) -> None:
|
|
345
260
|
"""Validate one protocol v1 event envelope.
|
|
346
261
|
|
|
347
262
|
With ``strict=True`` (the default), also enforces the recommended strict
|
|
348
|
-
protocol profile (required trace
|
|
349
|
-
|
|
350
|
-
envelope-shape check only.
|
|
263
|
+
protocol profile (required trace and required error.retryable). Pass
|
|
264
|
+
``strict=False`` for the plain, lenient envelope-shape check only.
|
|
351
265
|
"""
|
|
352
266
|
if not isinstance(event, dict):
|
|
353
267
|
raise ValueError("event must be a JSON object")
|
|
@@ -370,10 +284,6 @@ def validate_protocol_event(event: Any, *, strict: bool = True) -> None:
|
|
|
370
284
|
raise ValueError("event.trace is required by the strict profile")
|
|
371
285
|
if kind == "error":
|
|
372
286
|
_validate_strict_error_payload(event["error"])
|
|
373
|
-
elif kind == "log":
|
|
374
|
-
_validate_strict_log_payload(event["log"])
|
|
375
|
-
elif kind == "progress":
|
|
376
|
-
_validate_strict_progress_payload(event["progress"])
|
|
377
287
|
|
|
378
288
|
|
|
379
289
|
def _validate_error_payload(error: Any) -> None:
|
|
@@ -426,26 +336,6 @@ def _validate_strict_error_payload(error: Any) -> None:
|
|
|
426
336
|
raise ValueError("event.error.retryable must be a boolean in the strict profile")
|
|
427
337
|
|
|
428
338
|
|
|
429
|
-
def _validate_strict_log_payload(log: Any) -> None:
|
|
430
|
-
if not isinstance(log, dict):
|
|
431
|
-
raise ValueError("event.log must be a JSON object in the strict profile")
|
|
432
|
-
if "code" in log:
|
|
433
|
-
raise ValueError("event.log must not contain 'code' field in the strict profile")
|
|
434
|
-
_require_non_empty_string(log, "message", "event.log")
|
|
435
|
-
if "level" not in log:
|
|
436
|
-
raise ValueError("event.log.level is required by the strict profile")
|
|
437
|
-
if log.get("level") not in ("debug", "info", "warn", "error"):
|
|
438
|
-
raise ValueError(
|
|
439
|
-
"event.log.level must be one of debug, info, warn, error in the strict profile"
|
|
440
|
-
)
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
def _validate_strict_progress_payload(progress: Any) -> None:
|
|
444
|
-
if not isinstance(progress, dict):
|
|
445
|
-
raise ValueError("event.progress must be a JSON object in the strict profile")
|
|
446
|
-
_require_non_empty_string(progress, "message", "event.progress")
|
|
447
|
-
|
|
448
|
-
|
|
449
339
|
def _require_non_empty_string(payload: dict, field: str, path: str) -> None:
|
|
450
340
|
value = payload.get(field)
|
|
451
341
|
if not isinstance(value, str) or value == "":
|
|
@@ -487,22 +377,17 @@ class DecodedError:
|
|
|
487
377
|
|
|
488
378
|
@dataclass(frozen=True)
|
|
489
379
|
class DecodedProgress:
|
|
490
|
-
"""Decoded protocol v1 progress event.
|
|
491
|
-
beyond message."""
|
|
380
|
+
"""Decoded protocol v1 progress event."""
|
|
492
381
|
|
|
493
|
-
|
|
494
|
-
fields: dict[str, Any] = field(default_factory=dict)
|
|
382
|
+
progress: Any
|
|
495
383
|
trace: dict | None = None
|
|
496
384
|
|
|
497
385
|
|
|
498
386
|
@dataclass(frozen=True)
|
|
499
387
|
class DecodedLog:
|
|
500
|
-
"""Decoded protocol v1 log event.
|
|
501
|
-
beyond level/message."""
|
|
388
|
+
"""Decoded protocol v1 log event."""
|
|
502
389
|
|
|
503
|
-
|
|
504
|
-
message: str
|
|
505
|
-
fields: dict[str, Any] = field(default_factory=dict)
|
|
390
|
+
log: Any
|
|
506
391
|
trace: dict | None = None
|
|
507
392
|
|
|
508
393
|
|
|
@@ -544,16 +429,10 @@ def decode_protocol_event(
|
|
|
544
429
|
)
|
|
545
430
|
|
|
546
431
|
if kind == "progress":
|
|
547
|
-
progress
|
|
548
|
-
fields = {k: v for k, v in progress.items() if k != "message"}
|
|
549
|
-
return DecodedProgress(message=progress["message"], fields=fields, trace=trace)
|
|
432
|
+
return DecodedProgress(progress=event["progress"], trace=trace)
|
|
550
433
|
|
|
551
434
|
# kind == "log"
|
|
552
|
-
log
|
|
553
|
-
fields = {k: v for k, v in log.items() if k not in ("level", "message")}
|
|
554
|
-
return DecodedLog(
|
|
555
|
-
level=LogLevel(log["level"]), message=log["message"], fields=fields, trace=trace
|
|
556
|
-
)
|
|
435
|
+
return DecodedLog(log=event["log"], trace=trace)
|
|
557
436
|
|
|
558
437
|
|
|
559
438
|
# ═══════════════════════════════════════════
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-first-data
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.1
|
|
4
4
|
Summary: A naming convention that lets AI agents understand your data without being told what it means.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Repository, https://github.com/agentfirstkit/agent-first-data
|
|
@@ -140,7 +140,7 @@ def test_cli_output_dispatches_raw_yaml_with_options():
|
|
|
140
140
|
def test_cli_emitter_writes_events_and_tracks_terminal():
|
|
141
141
|
writer = StringIO()
|
|
142
142
|
emitter = CliEmitter(writer, OutputFormat.JSON)
|
|
143
|
-
emitter.emit(json_log(
|
|
143
|
+
emitter.emit(json_log({"level": "info", "message": "startup"}).build())
|
|
144
144
|
emitter.emit(json_result({"rows": 2}).build())
|
|
145
145
|
lines = writer.getvalue().splitlines()
|
|
146
146
|
assert len(lines) == 2
|
|
@@ -150,7 +150,7 @@ def test_cli_emitter_writes_events_and_tracks_terminal():
|
|
|
150
150
|
|
|
151
151
|
def test_cli_emitter_framing_all_formats():
|
|
152
152
|
events = [
|
|
153
|
-
json_log(
|
|
153
|
+
json_log({"level": "info", "message": "startup"}).build(),
|
|
154
154
|
json_result({"rows": 2}).build(),
|
|
155
155
|
]
|
|
156
156
|
for fmt in (OutputFormat.JSON, OutputFormat.PLAIN, OutputFormat.YAML):
|
|
@@ -238,13 +238,20 @@ def test_cli_emitter_convenience_methods():
|
|
|
238
238
|
def test_cli_emitter_with_log_fields_provider():
|
|
239
239
|
writer = StringIO()
|
|
240
240
|
def log_fields():
|
|
241
|
-
return {
|
|
241
|
+
return {
|
|
242
|
+
"source": "test",
|
|
243
|
+
"code": "cache_miss",
|
|
244
|
+
"message": "provider default",
|
|
245
|
+
"level": "debug",
|
|
246
|
+
}
|
|
242
247
|
emitter = CliEmitter(writer, OutputFormat.JSON, log_fields=log_fields)
|
|
243
248
|
emitter.emit_log(LogLevel.INFO, "test message")
|
|
244
249
|
lines = writer.getvalue().splitlines()
|
|
245
250
|
parsed = json.loads(lines[0])
|
|
246
251
|
assert parsed["log"]["source"] == "test"
|
|
252
|
+
assert parsed["log"]["code"] == "cache_miss"
|
|
247
253
|
assert parsed["log"]["message"] == "test message"
|
|
254
|
+
assert parsed["log"]["level"] == "info"
|
|
248
255
|
|
|
249
256
|
|
|
250
257
|
# ── version helpers ───────────────────────────────────────────────────────────
|
|
@@ -107,8 +107,8 @@ def _build_protocol_case_event(typ, args):
|
|
|
107
107
|
|
|
108
108
|
args vocabulary: "result" (payload), "code"+"message" (error),
|
|
109
109
|
"hint" (-> .hint()), "retryable" (bool -> .retryable_if()),
|
|
110
|
-
"fields" (object -> .fields()), "trace" (object -> .trace()),
|
|
111
|
-
|
|
110
|
+
"fields" (object -> .fields()), "trace" (object -> .trace()), and complete
|
|
111
|
+
progress/log payloads.
|
|
112
112
|
"""
|
|
113
113
|
kind = typ.split("_", 1)[0]
|
|
114
114
|
|
|
@@ -121,13 +121,13 @@ def _build_protocol_case_event(typ, args):
|
|
|
121
121
|
if "retryable" in args:
|
|
122
122
|
builder = builder.retryable_if(args["retryable"])
|
|
123
123
|
elif kind == "progress":
|
|
124
|
-
builder = json_progress(args["message"])
|
|
124
|
+
builder = json_progress({"message": args["message"], **args.get("fields", {})})
|
|
125
125
|
elif kind == "log":
|
|
126
|
-
builder = json_log(
|
|
126
|
+
builder = json_log({"level": args["level"], "message": args["message"], **args.get("fields", {})})
|
|
127
127
|
else:
|
|
128
128
|
raise ValueError(f"unknown fixture type: {typ}")
|
|
129
129
|
|
|
130
|
-
if "fields" in args:
|
|
130
|
+
if "fields" in args and kind not in ("progress", "log"):
|
|
131
131
|
builder = builder.fields(args["fields"])
|
|
132
132
|
if "trace" in args:
|
|
133
133
|
builder = builder.trace(args["trace"])
|
|
@@ -388,21 +388,18 @@ def test_decode_protocol_event_error():
|
|
|
388
388
|
|
|
389
389
|
|
|
390
390
|
def test_decode_protocol_event_progress():
|
|
391
|
-
event = json_progress("halfway"
|
|
391
|
+
event = json_progress({"message": "halfway", "percent": 50}).build()
|
|
392
392
|
decoded = decode_protocol_event(output_json(event.to_dict()))
|
|
393
393
|
assert isinstance(decoded, DecodedProgress)
|
|
394
|
-
assert decoded.
|
|
395
|
-
assert decoded.fields == {"percent": 50}
|
|
394
|
+
assert decoded.progress == {"message": "halfway", "percent": 50}
|
|
396
395
|
assert decoded.trace == {}
|
|
397
396
|
|
|
398
397
|
|
|
399
398
|
def test_decode_protocol_event_log():
|
|
400
|
-
event = json_log(
|
|
399
|
+
event = json_log({"level": "warn", "message": "slow query", "duration_ms": 900}).build()
|
|
401
400
|
decoded = decode_protocol_event(output_json(event.to_dict()))
|
|
402
401
|
assert isinstance(decoded, DecodedLog)
|
|
403
|
-
assert decoded.
|
|
404
|
-
assert decoded.message == "slow query"
|
|
405
|
-
assert decoded.fields == {"duration_ms": 900}
|
|
402
|
+
assert decoded.log == {"level": "warn", "message": "slow query", "duration_ms": 900}
|
|
406
403
|
assert decoded.trace == {}
|
|
407
404
|
|
|
408
405
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_first_data-0.16.0 → agent_first_data-0.16.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
|