cctally 1.91.0 → 1.92.0
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.
- package/CHANGELOG.md +39 -0
- package/bin/_cctally_cache.py +863 -74
- package/bin/_cctally_config.py +57 -0
- package/bin/_cctally_core.py +39 -8
- package/bin/_cctally_dashboard.py +146 -5
- package/bin/_cctally_dashboard_conversation.py +164 -18
- package/bin/_cctally_dashboard_envelope.py +2 -0
- package/bin/_cctally_db.py +372 -10
- package/bin/_cctally_doctor.py +18 -1
- package/bin/_cctally_journal.py +535 -13
- package/bin/_cctally_journal_repair.py +6 -0
- package/bin/_cctally_parser.py +6 -0
- package/bin/_cctally_quota.py +171 -55
- package/bin/_cctally_record.py +13 -1
- package/bin/_cctally_rederive.py +4 -0
- package/bin/_cctally_store.py +311 -6
- package/bin/_cctally_transcript.py +32 -2
- package/bin/_lib_cache_report.py +8 -3
- package/bin/_lib_codex_conversation.py +851 -81
- package/bin/_lib_codex_conversation_query.py +2005 -95
- package/bin/_lib_codex_find_projection.py +370 -0
- package/bin/_lib_codex_harness_preamble.py +176 -0
- package/bin/_lib_codex_hooks.py +5 -3
- package/bin/_lib_codex_js_scan.py +254 -0
- package/bin/_lib_codex_landmarks.py +309 -0
- package/bin/_lib_codex_title_clean.py +116 -0
- package/bin/_lib_conversation_dispatch.py +153 -21
- package/bin/_lib_conversation_watch.py +4 -2
- package/bin/_lib_doctor.py +64 -0
- package/bin/_lib_quota_alert_axes.py +31 -34
- package/bin/_lib_stats_damage.py +523 -0
- package/bin/cctally +5 -0
- package/dashboard/static/assets/index-BEzzJtUd.js +97 -0
- package/dashboard/static/assets/{index-Dwirao3Y.css → index-DnWdv8um.css} +1 -1
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +7 -1
- package/dashboard/static/assets/index-CILAoEja.js +0 -90
|
@@ -25,6 +25,8 @@ from typing import Any, Iterable
|
|
|
25
25
|
# _lib_conversation_query. These modules are stdlib-only pure kernels.
|
|
26
26
|
from _lib_conversation import _strip_ansi
|
|
27
27
|
from _lib_conversation_query import _TITLE_MAX, _first_nonblank_line
|
|
28
|
+
from _lib_codex_harness_preamble import parse_harness_preamble
|
|
29
|
+
from _lib_codex_js_scan import scan_tool_invocations
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
# ── digest contract (§3.1) ────────────────────────────────────────────────────
|
|
@@ -252,6 +254,17 @@ _EXEC_METADATA_KEYS = frozenset({
|
|
|
252
254
|
"justification", "login", "max_output_tokens", "prefix_rule",
|
|
253
255
|
"sandbox_permissions", "shell", "tty", "yield_time_ms",
|
|
254
256
|
})
|
|
257
|
+
# The FROZEN v1 ingest projection, kept deliberately rather than deleted.
|
|
258
|
+
#
|
|
259
|
+
# Live decoding moved to `_lib_codex_harness_preamble.parse_harness_preamble`
|
|
260
|
+
# (#463 S3, spec section 4.1), which this regex could never do: it requires a
|
|
261
|
+
# blank line before `Output:` and end-of-string after it, and 0 of the 39,942
|
|
262
|
+
# production outputs carrying the preamble it targets match it. But `_extract`
|
|
263
|
+
# must keep storing byte-identical `detail_json` (spec section 3.0), because
|
|
264
|
+
# `_row_source_bytes` feeds `PAGE_SOURCE_BYTE_BUDGET` and a richer stored card
|
|
265
|
+
# would move page boundaries for newly ingested rows only. So this stays as the
|
|
266
|
+
# stored contract until `CODEX_CONVERSATION_CONTRACT_VERSION` is bumped and the
|
|
267
|
+
# history is replayed. It is reached ONLY through `for_storage=True`.
|
|
255
268
|
_HARNESS_STATUS_RE = re.compile(
|
|
256
269
|
r"\A(Script completed|Script failed)\nWall time ([^\n]+)\n\nOutput:\Z")
|
|
257
270
|
|
|
@@ -451,14 +464,85 @@ def _json_object(value: Any) -> dict | None:
|
|
|
451
464
|
return parsed if isinstance(parsed, dict) else None
|
|
452
465
|
|
|
453
466
|
|
|
467
|
+
def _decode_shell_family_card(
|
|
468
|
+
payload: dict, name: Any, budget: _TextBudget, text_cap: int,
|
|
469
|
+
) -> dict | None:
|
|
470
|
+
"""The four uncarded `function_call` families (spec section 3.2).
|
|
471
|
+
|
|
472
|
+
`exec_command` maps onto the EXISTING `terminal` card rather than a new type,
|
|
473
|
+
so `BashCard` renders it unchanged. `write_stdin` and `wait` produce
|
|
474
|
+
`session_ref`; grouping happens only at `shell` scope, and a cell reference
|
|
475
|
+
is never presented as a shell session. `js` produces the `program` card of
|
|
476
|
+
section 3.3 carrying its authored `title`, which is the only authored
|
|
477
|
+
description of a program available anywhere in the payload.
|
|
478
|
+
"""
|
|
479
|
+
if name == "exec_command":
|
|
480
|
+
arguments = _json_object(payload.get("arguments"))
|
|
481
|
+
command = _command_invocation(arguments, budget)
|
|
482
|
+
if command is None:
|
|
483
|
+
return None
|
|
484
|
+
card = {
|
|
485
|
+
"schema_version": CODEX_CARD_SCHEMA_VERSION,
|
|
486
|
+
"type": "terminal",
|
|
487
|
+
"status": (payload.get("status")
|
|
488
|
+
if isinstance(payload.get("status"), str) else "unknown"),
|
|
489
|
+
"commands": [command],
|
|
490
|
+
}
|
|
491
|
+
if budget.truncated:
|
|
492
|
+
card["truncated"] = True
|
|
493
|
+
return card
|
|
494
|
+
if name in _SESSION_TOOLS:
|
|
495
|
+
arguments = _json_object(payload.get("arguments"))
|
|
496
|
+
session = _session_invocation(name, arguments, budget)
|
|
497
|
+
if session is None:
|
|
498
|
+
return None
|
|
499
|
+
return {
|
|
500
|
+
"schema_version": CODEX_CARD_SCHEMA_VERSION,
|
|
501
|
+
"type": "session_ref", **session, "truncated": budget.truncated,
|
|
502
|
+
}
|
|
503
|
+
if name == "js":
|
|
504
|
+
arguments = _json_object(payload.get("arguments"))
|
|
505
|
+
if arguments is None:
|
|
506
|
+
return None
|
|
507
|
+
title = arguments.get("title")
|
|
508
|
+
if title is not None and not isinstance(title, str):
|
|
509
|
+
return None
|
|
510
|
+
return _program_card(
|
|
511
|
+
arguments.get("code"),
|
|
512
|
+
title=budget.take(title) if isinstance(title, str) else None,
|
|
513
|
+
text_cap=text_cap)
|
|
514
|
+
if name == "tool_search_call":
|
|
515
|
+
arguments = _json_object(payload.get("arguments"))
|
|
516
|
+
if arguments is None or not isinstance(arguments.get("query"), str):
|
|
517
|
+
return None
|
|
518
|
+
limit = arguments.get("limit")
|
|
519
|
+
if limit is not None and (not isinstance(limit, int) or isinstance(limit, bool)):
|
|
520
|
+
return None
|
|
521
|
+
card = {
|
|
522
|
+
"schema_version": CODEX_CARD_SCHEMA_VERSION,
|
|
523
|
+
"type": "tool_search", "query": budget.take(arguments["query"]),
|
|
524
|
+
"limit": limit,
|
|
525
|
+
}
|
|
526
|
+
if budget.truncated:
|
|
527
|
+
card["truncated"] = True
|
|
528
|
+
return card
|
|
529
|
+
return None
|
|
530
|
+
|
|
531
|
+
|
|
454
532
|
def decode_secondary_tool_call_card(
|
|
455
|
-
payload: dict, *, text_cap: int = CODEX_TEXT_CAP,
|
|
533
|
+
payload: dict, *, text_cap: int = CODEX_TEXT_CAP, for_storage: bool = False,
|
|
456
534
|
) -> dict | None:
|
|
457
|
-
"""Bounded additive wire for plans, web actions,
|
|
535
|
+
"""Bounded additive wire for plans, web actions, agent operations, and the
|
|
536
|
+
shell/program families #463 S3 adds.
|
|
458
537
|
|
|
459
538
|
Unknown or malformed shapes deliberately return ``None`` so the existing
|
|
460
539
|
provider-name + raw-argument fallback remains visible and payload readback
|
|
461
540
|
stays authoritative.
|
|
541
|
+
|
|
542
|
+
``for_storage=True`` produces the FROZEN v1 projection ``_extract`` persists
|
|
543
|
+
(spec section 3.0), which means the five S3 families decode to nothing: they
|
|
544
|
+
reach 34,935 rows, more than any other S3 change, and persisting them would
|
|
545
|
+
move `PAGE_SOURCE_BYTE_BUDGET` boundaries for newly ingested rows only.
|
|
462
546
|
"""
|
|
463
547
|
if not isinstance(payload, dict):
|
|
464
548
|
return None
|
|
@@ -466,6 +550,10 @@ def decode_secondary_tool_call_card(
|
|
|
466
550
|
name = payload.get("name")
|
|
467
551
|
status = payload.get("status") if isinstance(payload.get("status"), str) else "requested"
|
|
468
552
|
budget = _TextBudget(text_cap)
|
|
553
|
+
if not for_storage and ptype == "function_call":
|
|
554
|
+
s3_card = _decode_shell_family_card(payload, name, budget, text_cap)
|
|
555
|
+
if s3_card is not None:
|
|
556
|
+
return s3_card
|
|
469
557
|
if ptype == "function_call" and name == "update_plan":
|
|
470
558
|
arguments = _json_object(payload.get("arguments"))
|
|
471
559
|
plan = arguments.get("plan") if isinstance(arguments, dict) else None
|
|
@@ -590,7 +678,12 @@ def decode_secondary_event_card(
|
|
|
590
678
|
return None
|
|
591
679
|
result = payload.get("result")
|
|
592
680
|
if isinstance(result, dict) and "Ok" in result:
|
|
593
|
-
|
|
681
|
+
ok_result = result.get("Ok")
|
|
682
|
+
status = (
|
|
683
|
+
"error"
|
|
684
|
+
if isinstance(ok_result, dict) and ok_result.get("isError") is True
|
|
685
|
+
else "ok"
|
|
686
|
+
)
|
|
594
687
|
elif isinstance(result, dict) and any(key in result for key in ("Err", "Error")):
|
|
595
688
|
status = "error"
|
|
596
689
|
else:
|
|
@@ -612,8 +705,77 @@ def decode_secondary_event_card(
|
|
|
612
705
|
return None
|
|
613
706
|
|
|
614
707
|
|
|
615
|
-
|
|
616
|
-
|
|
708
|
+
# The two tools that name a session rather than run a command, and what each
|
|
709
|
+
# names. `session_id` and `cell_id` are NOT two spellings of one thing: the
|
|
710
|
+
# namespaces have zero overlapping values, and a `cell_id` is a per-conversation
|
|
711
|
+
# sandbox-cell ordinal. `wait` polls a cell; it does not poll a shell session.
|
|
712
|
+
_SESSION_TOOLS = {
|
|
713
|
+
"write_stdin": ("shell", "write"),
|
|
714
|
+
"wait": ("cell", "poll"),
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
# `_exec_chain` outcomes. The distinction matters: a source that is not the
|
|
718
|
+
# strict chain grammar at all may still be a legible PROGRAM, while one that has
|
|
719
|
+
# the chain's shape and was refused by the literal parser or the command cap must
|
|
720
|
+
# keep producing no card, which is what the shipped closed-parser regression
|
|
721
|
+
# requires for `process.env.SECRET`, a template argument and nine invocations.
|
|
722
|
+
_EXEC_OK = "ok"
|
|
723
|
+
_EXEC_SHAPE = "shape"
|
|
724
|
+
_EXEC_REFUSED = "refused"
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
def _command_invocation(value: Any, budget: _TextBudget) -> dict | None:
|
|
728
|
+
"""One `exec_command` argument object as the shared `terminal` command."""
|
|
729
|
+
if not isinstance(value, dict) or not isinstance(value.get("cmd"), str):
|
|
730
|
+
return None
|
|
731
|
+
workdir = value.get("workdir")
|
|
732
|
+
if workdir is not None and not isinstance(workdir, str):
|
|
733
|
+
return None
|
|
734
|
+
command = {
|
|
735
|
+
"workdir": budget.take(workdir) if isinstance(workdir, str) else None,
|
|
736
|
+
"command": "",
|
|
737
|
+
"metadata": {},
|
|
738
|
+
}
|
|
739
|
+
command["command"] = budget.take(value["cmd"])
|
|
740
|
+
for key in sorted(_EXEC_METADATA_KEYS):
|
|
741
|
+
if key not in value:
|
|
742
|
+
continue
|
|
743
|
+
bounded = _bounded_metadata(value[key], budget)
|
|
744
|
+
if bounded is not None:
|
|
745
|
+
command["metadata"][key] = bounded
|
|
746
|
+
return command
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
def _session_invocation(name: str, value: Any, budget: _TextBudget) -> dict | None:
|
|
750
|
+
"""One `write_stdin`/`wait` argument object as a session reference.
|
|
751
|
+
|
|
752
|
+
``ref`` carries the provider's own identifier here, because the kernel has no
|
|
753
|
+
conversation context. The query layer replaces a ``shell`` ref with the
|
|
754
|
+
conversation-local ordinal before publishing, so no raw provider session id
|
|
755
|
+
ever reaches a reader (spec section 4.3).
|
|
756
|
+
"""
|
|
757
|
+
if not isinstance(value, dict):
|
|
758
|
+
return None
|
|
759
|
+
scope, operation = _SESSION_TOOLS[name]
|
|
760
|
+
raw = value.get("session_id") if scope == "shell" else value.get("cell_id")
|
|
761
|
+
if isinstance(raw, bool) or not isinstance(raw, (str, int)):
|
|
762
|
+
return None
|
|
763
|
+
chars = value.get("chars")
|
|
764
|
+
if chars is not None and not isinstance(chars, str):
|
|
765
|
+
return None
|
|
766
|
+
return {
|
|
767
|
+
"scope": scope, "ref": budget.take(str(raw)), "operation": operation,
|
|
768
|
+
"chars": budget.take(chars) if isinstance(chars, str) else None,
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
def _exec_chain(source: str, *, budget: _TextBudget) -> tuple[str, list[dict] | None]:
|
|
773
|
+
"""Decode only the complete current exec harness statement grammar.
|
|
774
|
+
|
|
775
|
+
Returns ``(_EXEC_OK, commands)``, ``(_EXEC_SHAPE, None)`` when the source is
|
|
776
|
+
not that grammar, or ``(_EXEC_REFUSED, None)`` when it HAS the grammar's
|
|
777
|
+
shape but a literal or the command cap refused it.
|
|
778
|
+
"""
|
|
617
779
|
commands: list[dict] = []
|
|
618
780
|
pos = 0
|
|
619
781
|
while True:
|
|
@@ -622,8 +784,10 @@ def _exec_invocations(source: str, *, budget: _TextBudget) -> list[dict] | None:
|
|
|
622
784
|
r"await\s+tools\.exec_command",
|
|
623
785
|
source[pos:],
|
|
624
786
|
)
|
|
625
|
-
if prefix is None
|
|
626
|
-
return None
|
|
787
|
+
if prefix is None:
|
|
788
|
+
return _EXEC_SHAPE, None
|
|
789
|
+
if len(commands) >= _CARD_MAX_COMMANDS:
|
|
790
|
+
return _EXEC_REFUSED, None
|
|
627
791
|
variable = prefix.group(1)
|
|
628
792
|
parser = _HarnessLiteralParser(source, pos + prefix.end())
|
|
629
793
|
try:
|
|
@@ -631,24 +795,10 @@ def _exec_invocations(source: str, *, budget: _TextBudget) -> list[dict] | None:
|
|
|
631
795
|
value = parser.value()
|
|
632
796
|
parser._consume(")")
|
|
633
797
|
except _LiteralError:
|
|
634
|
-
return None
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
if workdir is not None and not isinstance(workdir, str):
|
|
639
|
-
return None
|
|
640
|
-
command = {
|
|
641
|
-
"workdir": budget.take(workdir) if isinstance(workdir, str) else None,
|
|
642
|
-
"command": "",
|
|
643
|
-
"metadata": {},
|
|
644
|
-
}
|
|
645
|
-
command["command"] = budget.take(value["cmd"])
|
|
646
|
-
for key in sorted(_EXEC_METADATA_KEYS):
|
|
647
|
-
if key not in value:
|
|
648
|
-
continue
|
|
649
|
-
bounded = _bounded_metadata(value[key], budget)
|
|
650
|
-
if bounded is not None:
|
|
651
|
-
command["metadata"][key] = bounded
|
|
798
|
+
return _EXEC_REFUSED, None
|
|
799
|
+
command = _command_invocation(value, budget)
|
|
800
|
+
if command is None:
|
|
801
|
+
return _EXEC_REFUSED, None
|
|
652
802
|
commands.append(command)
|
|
653
803
|
suffix = re.match(
|
|
654
804
|
r"\s*;\s*text\s*\(\s*" + re.escape(variable)
|
|
@@ -656,10 +806,92 @@ def _exec_invocations(source: str, *, budget: _TextBudget) -> list[dict] | None:
|
|
|
656
806
|
source[parser.pos:],
|
|
657
807
|
)
|
|
658
808
|
if suffix is None:
|
|
659
|
-
return None
|
|
809
|
+
return _EXEC_SHAPE, None
|
|
660
810
|
pos = parser.pos + suffix.end()
|
|
661
811
|
if not source[pos:].strip():
|
|
662
|
-
return commands
|
|
812
|
+
return _EXEC_OK, commands
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
def _scanned_invocations(
|
|
816
|
+
source: str, *, budget: _TextBudget,
|
|
817
|
+
) -> tuple[list[dict], bool] | None:
|
|
818
|
+
"""``(invocations, over_cap)`` for a program, or ``None`` if it cannot be lexed.
|
|
819
|
+
|
|
820
|
+
Arguments are read by the existing ``_HarnessLiteralParser``, so a non-literal
|
|
821
|
+
such as ``process.env.SECRET`` is still refused — the entry degrades to
|
|
822
|
+
``other``, which names the tool and claims nothing about what it was given.
|
|
823
|
+
"""
|
|
824
|
+
located = scan_tool_invocations(source, limit=_CARD_MAX_COMMANDS + 1)
|
|
825
|
+
if located is None:
|
|
826
|
+
return None
|
|
827
|
+
invocations: list[dict] = []
|
|
828
|
+
for name, paren in located[:_CARD_MAX_COMMANDS]:
|
|
829
|
+
parser = _HarnessLiteralParser(source, paren)
|
|
830
|
+
try:
|
|
831
|
+
parser._consume("(")
|
|
832
|
+
value = parser.value()
|
|
833
|
+
except _LiteralError:
|
|
834
|
+
value = None
|
|
835
|
+
entry: dict | None = None
|
|
836
|
+
if name == "exec_command":
|
|
837
|
+
command = _command_invocation(value, budget)
|
|
838
|
+
if command is not None:
|
|
839
|
+
entry = {"kind": "command", **command}
|
|
840
|
+
elif name in _SESSION_TOOLS:
|
|
841
|
+
session = _session_invocation(name, value, budget)
|
|
842
|
+
if session is not None:
|
|
843
|
+
entry = {"kind": "session", **session}
|
|
844
|
+
if entry is None:
|
|
845
|
+
entry = {"kind": "other", "name": budget.take(name)}
|
|
846
|
+
invocations.append(entry)
|
|
847
|
+
return invocations, len(located) > _CARD_MAX_COMMANDS
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
def _program_card_from_scan(
|
|
851
|
+
source: str, *, title: str | None, budget: _TextBudget,
|
|
852
|
+
) -> dict | None:
|
|
853
|
+
"""A `program` card for a source the strict chain grammar did not match."""
|
|
854
|
+
scanned = _scanned_invocations(source, budget=budget)
|
|
855
|
+
if scanned is None:
|
|
856
|
+
return None
|
|
857
|
+
invocations, over_cap = scanned
|
|
858
|
+
if not invocations:
|
|
859
|
+
return None
|
|
860
|
+
return {
|
|
861
|
+
"schema_version": CODEX_CARD_SCHEMA_VERSION,
|
|
862
|
+
"type": "program", "title": title,
|
|
863
|
+
# Never claim the program did only what is listed: the scanner models
|
|
864
|
+
# `tools.<name>(` and nothing else, so anything reaching here contains
|
|
865
|
+
# statements it did not read.
|
|
866
|
+
"complete": False,
|
|
867
|
+
"invocations": invocations,
|
|
868
|
+
"truncated": over_cap or budget.truncated,
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
def _program_card(
|
|
873
|
+
source: Any, *, title: str | None, text_cap: int,
|
|
874
|
+
) -> dict | None:
|
|
875
|
+
"""A `program` card for an arbitrary authored program body.
|
|
876
|
+
|
|
877
|
+
A body that IS the complete recognized chain is still a program card — the
|
|
878
|
+
`js` family always renders as one — but it is marked `complete`.
|
|
879
|
+
"""
|
|
880
|
+
if not isinstance(source, str) or len(source) > _CARD_HARNESS_PARSE_CAP:
|
|
881
|
+
return None
|
|
882
|
+
budget = _TextBudget(text_cap)
|
|
883
|
+
outcome, commands = _exec_chain(source, budget=budget)
|
|
884
|
+
if outcome == _EXEC_REFUSED:
|
|
885
|
+
return None
|
|
886
|
+
if outcome == _EXEC_OK:
|
|
887
|
+
return {
|
|
888
|
+
"schema_version": CODEX_CARD_SCHEMA_VERSION,
|
|
889
|
+
"type": "program", "title": title, "complete": True,
|
|
890
|
+
"invocations": [{"kind": "command", **command} for command in commands],
|
|
891
|
+
"truncated": budget.truncated,
|
|
892
|
+
}
|
|
893
|
+
return _program_card_from_scan(
|
|
894
|
+
source, title=title, budget=_TextBudget(text_cap))
|
|
663
895
|
|
|
664
896
|
|
|
665
897
|
def _decode_apply_patch_program(source: str) -> str | None:
|
|
@@ -694,23 +926,141 @@ def _apply_patch_heredoc(command: str) -> str | None:
|
|
|
694
926
|
return None
|
|
695
927
|
|
|
696
928
|
|
|
929
|
+
_APPLY_PATCH_ACTION_RE = re.compile(r"\*\*\* (Add|Update|Delete) File: (.+)\Z")
|
|
930
|
+
_APPLY_PATCH_MOVE_RE = re.compile(r"\*\*\* Move to: (.+)\Z")
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
def _apply_patch_row_kind(line: str) -> str:
|
|
934
|
+
"""Classify one V4A body row the way the client's diff parser does."""
|
|
935
|
+
head = line[:1]
|
|
936
|
+
if head == "+":
|
|
937
|
+
return "add"
|
|
938
|
+
if head == "-":
|
|
939
|
+
return "del"
|
|
940
|
+
if head == "\\":
|
|
941
|
+
return "marker"
|
|
942
|
+
return "context"
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
def _apply_patch_unified_diff(
|
|
946
|
+
old_path: str, new_path: str, body: list[str],
|
|
947
|
+
) -> str | None:
|
|
948
|
+
"""One section of a proven ``apply_patch`` envelope as a unified diff.
|
|
949
|
+
|
|
950
|
+
The V4A envelope states no line offsets — its ``@@`` markers carry context
|
|
951
|
+
TEXT, not numbers — so each hunk header counts the rows it contains and
|
|
952
|
+
numbers them relative to the file's first hunk. That is the same relative
|
|
953
|
+
numbering the edit-diff card has always rendered under, and the running
|
|
954
|
+
counts continue across a file's hunks so two hunks never show one gutter
|
|
955
|
+
number twice.
|
|
956
|
+
|
|
957
|
+
A section with no body row at all — a V4A ``*** Delete File:`` names the
|
|
958
|
+
file and carries nothing — yields ``None``, so the card claims no diff
|
|
959
|
+
rather than publishing an empty one.
|
|
960
|
+
"""
|
|
961
|
+
hunks: list[list[str]] = []
|
|
962
|
+
current: list[str] | None = None
|
|
963
|
+
for line in body:
|
|
964
|
+
if line.startswith("@@"):
|
|
965
|
+
current = []
|
|
966
|
+
hunks.append(current)
|
|
967
|
+
continue
|
|
968
|
+
if current is None:
|
|
969
|
+
current = []
|
|
970
|
+
hunks.append(current)
|
|
971
|
+
current.append(line)
|
|
972
|
+
hunks = [hunk for hunk in hunks if hunk]
|
|
973
|
+
if not hunks:
|
|
974
|
+
return None
|
|
975
|
+
out = [f"--- {old_path}\n", f"+++ {new_path}\n"]
|
|
976
|
+
old_at = new_at = 1
|
|
977
|
+
for hunk in hunks:
|
|
978
|
+
kinds = [_apply_patch_row_kind(line) for line in hunk]
|
|
979
|
+
old_count = sum(1 for kind in kinds if kind in ("context", "del"))
|
|
980
|
+
new_count = sum(1 for kind in kinds if kind in ("context", "add"))
|
|
981
|
+
out.append(
|
|
982
|
+
f"@@ -{old_at if old_count else 0},{old_count} "
|
|
983
|
+
f"+{new_at if new_count else 0},{new_count} @@\n")
|
|
984
|
+
out.extend(f"{line}\n" for line in hunk)
|
|
985
|
+
old_at += old_count
|
|
986
|
+
new_at += new_count
|
|
987
|
+
return "".join(out)
|
|
988
|
+
|
|
989
|
+
|
|
697
990
|
def _patch_files_from_apply_patch(
|
|
698
|
-
patch: str, budget: _TextBudget | None = None,
|
|
991
|
+
patch: str, budget: _TextBudget | None = None, *, with_diffs: bool = False,
|
|
699
992
|
) -> list[dict]:
|
|
993
|
+
"""The file entries of a proven ``apply_patch`` envelope.
|
|
994
|
+
|
|
995
|
+
``with_diffs`` is READ TIME only. The stored v1 projection is the file LIST
|
|
996
|
+
it has always been, because `detail_json` bytes feed `_row_source_bytes` and
|
|
997
|
+
therefore page boundaries; publishing the diffs there would paginate a newly
|
|
998
|
+
ingested conversation differently from an identical historical one.
|
|
999
|
+
|
|
1000
|
+
At read time the entries gain the same field vocabulary the
|
|
1001
|
+
``patch_apply_end`` card publishes — ``unified_diff``, ``diff_source`` and
|
|
1002
|
+
per-file ``truncated`` — through the same `_allocate_diff_budget`
|
|
1003
|
+
allocation, so one patch does not read two ways depending on which side of
|
|
1004
|
+
it the reader is looking at.
|
|
1005
|
+
"""
|
|
700
1006
|
files: list[dict] = []
|
|
1007
|
+
raw_paths: list[str] = []
|
|
1008
|
+
bodies: list[list[str]] = []
|
|
1009
|
+
# Past the file cap a section is dropped whole, so its body must not be
|
|
1010
|
+
# attributed to the previous file's diff.
|
|
1011
|
+
collecting = False
|
|
701
1012
|
for line in patch.splitlines():
|
|
702
|
-
match =
|
|
703
|
-
if match is not None
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
1013
|
+
match = _APPLY_PATCH_ACTION_RE.match(line)
|
|
1014
|
+
if match is not None:
|
|
1015
|
+
collecting = len(files) < _CARD_MAX_FILES
|
|
1016
|
+
if collecting:
|
|
1017
|
+
status = {"Add": "added", "Update": "modified", "Delete": "deleted"}[
|
|
1018
|
+
match.group(1)]
|
|
1019
|
+
path = (budget.take(match.group(2)) if budget is not None
|
|
1020
|
+
else match.group(2))
|
|
1021
|
+
files.append({"path": path, "status": status})
|
|
1022
|
+
raw_paths.append(match.group(2))
|
|
1023
|
+
bodies.append([])
|
|
708
1024
|
continue
|
|
709
|
-
move =
|
|
710
|
-
if move is not None and files:
|
|
1025
|
+
move = _APPLY_PATCH_MOVE_RE.match(line)
|
|
1026
|
+
if move is not None and collecting and files:
|
|
711
1027
|
files[-1]["move_path"] = (
|
|
712
1028
|
budget.take(move.group(1)) if budget is not None else move.group(1))
|
|
713
1029
|
files[-1]["status"] = "moved"
|
|
1030
|
+
continue
|
|
1031
|
+
if line.startswith("*** "):
|
|
1032
|
+
continue
|
|
1033
|
+
if collecting and bodies:
|
|
1034
|
+
bodies[-1].append(line)
|
|
1035
|
+
if not with_diffs:
|
|
1036
|
+
return files
|
|
1037
|
+
diffs: list[str | None] = []
|
|
1038
|
+
for entry, raw_path, body in zip(files, raw_paths, bodies):
|
|
1039
|
+
# The header names the PROVIDER's own path, which can differ from the
|
|
1040
|
+
# entry's when the budget clipped the latter — the same rule the
|
|
1041
|
+
# synthesized event-side diff follows.
|
|
1042
|
+
move_path = entry.get("move_path")
|
|
1043
|
+
if entry["status"] == "added":
|
|
1044
|
+
old_path, new_path = "/dev/null", raw_path
|
|
1045
|
+
elif entry["status"] == "deleted":
|
|
1046
|
+
old_path, new_path = raw_path, "/dev/null"
|
|
1047
|
+
else:
|
|
1048
|
+
old_path, new_path = raw_path, move_path or raw_path
|
|
1049
|
+
diff = _apply_patch_unified_diff(old_path, new_path, body)
|
|
1050
|
+
# `truncated` on EVERY entry, so a client never has to read an absent
|
|
1051
|
+
# key as false; `_allocate_diff_budget` overwrites it where a diff runs.
|
|
1052
|
+
entry["truncated"] = False
|
|
1053
|
+
if diff is not None:
|
|
1054
|
+
# The provider transmitted a patch envelope, not a unified diff, so
|
|
1055
|
+
# this is a rendering of retained content — never `retained`.
|
|
1056
|
+
entry["diff_source"] = "derived"
|
|
1057
|
+
diffs.append(diff)
|
|
1058
|
+
if budget is not None:
|
|
1059
|
+
_allocate_diff_budget(files, diffs, budget)
|
|
1060
|
+
else:
|
|
1061
|
+
for entry, diff in zip(files, diffs):
|
|
1062
|
+
if diff is not None:
|
|
1063
|
+
entry["unified_diff"] = diff
|
|
714
1064
|
return files
|
|
715
1065
|
|
|
716
1066
|
|
|
@@ -726,8 +1076,16 @@ def _complete_apply_patch(patch: str) -> bool:
|
|
|
726
1076
|
for line in lines[1:-1])
|
|
727
1077
|
|
|
728
1078
|
|
|
729
|
-
def decode_tool_call_card(
|
|
730
|
-
|
|
1079
|
+
def decode_tool_call_card(
|
|
1080
|
+
payload: dict, *, text_cap: int = CODEX_TEXT_CAP, for_storage: bool = False,
|
|
1081
|
+
) -> dict | None:
|
|
1082
|
+
"""Return the additive card contract for a structurally proven call.
|
|
1083
|
+
|
|
1084
|
+
``for_storage=True`` produces the FROZEN v1 projection ``_extract`` persists
|
|
1085
|
+
(spec section 3.0): the `program` card is a read-time addition and is not
|
|
1086
|
+
stored, because it would newly appear on 17,777 rows and move
|
|
1087
|
+
`PAGE_SOURCE_BYTE_BUDGET` boundaries for whichever binary ingested them.
|
|
1088
|
+
"""
|
|
731
1089
|
if not isinstance(payload, dict) or payload.get("type") != "custom_tool_call":
|
|
732
1090
|
return None
|
|
733
1091
|
name = payload.get("name")
|
|
@@ -739,7 +1097,11 @@ def decode_tool_call_card(payload: dict, *, text_cap: int = CODEX_TEXT_CAP) -> d
|
|
|
739
1097
|
budget = _TextBudget(text_cap)
|
|
740
1098
|
status = payload.get("status") if isinstance(payload.get("status"), str) else "unknown"
|
|
741
1099
|
if name == "apply_patch" and _complete_apply_patch(value):
|
|
742
|
-
|
|
1100
|
+
# The derived diffs are taken from the shared budget BEFORE the raw
|
|
1101
|
+
# `patch`: `patch` is the payload disclosure's copy and no card renders
|
|
1102
|
+
# it, while the diffs are the only thing on this card a reader reads.
|
|
1103
|
+
files = _patch_files_from_apply_patch(
|
|
1104
|
+
value, budget, with_diffs=not for_storage)
|
|
743
1105
|
patch = budget.take(value)
|
|
744
1106
|
return {
|
|
745
1107
|
"schema_version": CODEX_CARD_SCHEMA_VERSION,
|
|
@@ -751,7 +1113,8 @@ def decode_tool_call_card(payload: dict, *, text_cap: int = CODEX_TEXT_CAP) -> d
|
|
|
751
1113
|
return None
|
|
752
1114
|
patch_value = _decode_apply_patch_program(value)
|
|
753
1115
|
if patch_value is not None and _complete_apply_patch(patch_value):
|
|
754
|
-
files = _patch_files_from_apply_patch(
|
|
1116
|
+
files = _patch_files_from_apply_patch(
|
|
1117
|
+
patch_value, budget, with_diffs=not for_storage)
|
|
755
1118
|
patch = budget.take(patch_value)
|
|
756
1119
|
return {
|
|
757
1120
|
"schema_version": CODEX_CARD_SCHEMA_VERSION,
|
|
@@ -759,13 +1122,26 @@ def decode_tool_call_card(payload: dict, *, text_cap: int = CODEX_TEXT_CAP) -> d
|
|
|
759
1122
|
"patch": patch, "files": files,
|
|
760
1123
|
"truncated": budget.truncated,
|
|
761
1124
|
}
|
|
762
|
-
commands =
|
|
763
|
-
if
|
|
1125
|
+
outcome, commands = _exec_chain(value, budget=budget)
|
|
1126
|
+
if outcome == _EXEC_REFUSED:
|
|
1127
|
+
# The chain's SHAPE with a literal or the command cap refusing it. This
|
|
1128
|
+
# is the arm the shipped closed-parser regression pins: a
|
|
1129
|
+
# `process.env.SECRET` argument, a template argument and nine
|
|
1130
|
+
# invocations must all keep producing no card at all.
|
|
764
1131
|
return None
|
|
1132
|
+
if outcome == _EXEC_SHAPE:
|
|
1133
|
+
# Not the chain grammar — but 17,777 uncarded `exec` calls are programs
|
|
1134
|
+
# that declare constants, filter collections and invoke two different
|
|
1135
|
+
# tool families, and one `terminal` card cannot express that.
|
|
1136
|
+
if for_storage:
|
|
1137
|
+
return None
|
|
1138
|
+
return _program_card_from_scan(
|
|
1139
|
+
value, title=None, budget=_TextBudget(text_cap))
|
|
765
1140
|
if len(commands) == 1:
|
|
766
1141
|
heredoc = _apply_patch_heredoc(commands[0]["command"])
|
|
767
1142
|
if heredoc is not None and _complete_apply_patch(heredoc):
|
|
768
|
-
files = _patch_files_from_apply_patch(
|
|
1143
|
+
files = _patch_files_from_apply_patch(
|
|
1144
|
+
heredoc, budget, with_diffs=not for_storage)
|
|
769
1145
|
return {
|
|
770
1146
|
"schema_version": CODEX_CARD_SCHEMA_VERSION,
|
|
771
1147
|
"type": "patch", "source": "exec_apply_patch", "status": status,
|
|
@@ -781,10 +1157,33 @@ def decode_tool_call_card(payload: dict, *, text_cap: int = CODEX_TEXT_CAP) -> d
|
|
|
781
1157
|
return card
|
|
782
1158
|
|
|
783
1159
|
|
|
1160
|
+
def _output_head_text(value: Any) -> str | None:
|
|
1161
|
+
"""The text of an output's FIRST part, for the two shapes that carry one.
|
|
1162
|
+
|
|
1163
|
+
Both the array and the bare-string envelope occur for both record types, and
|
|
1164
|
+
the preamble is positionally guaranteed to be at the head of whichever one
|
|
1165
|
+
arrives, so this is the only place it may be looked for (spec section 4.1).
|
|
1166
|
+
"""
|
|
1167
|
+
if isinstance(value, str):
|
|
1168
|
+
return value
|
|
1169
|
+
if (isinstance(value, dict) and value.get("type") == "input_text"
|
|
1170
|
+
and isinstance(value.get("text"), str)):
|
|
1171
|
+
return value["text"]
|
|
1172
|
+
return None
|
|
1173
|
+
|
|
1174
|
+
|
|
784
1175
|
def decode_tool_output_card(
|
|
785
|
-
payload: dict, *, text_cap: int = CODEX_TEXT_CAP,
|
|
1176
|
+
payload: dict, *, text_cap: int = CODEX_TEXT_CAP, for_storage: bool = False,
|
|
786
1177
|
) -> tuple[dict, str] | None:
|
|
787
|
-
"""Unwrap supported output envelopes without losing malformed parts.
|
|
1178
|
+
"""Unwrap supported output envelopes without losing malformed parts.
|
|
1179
|
+
|
|
1180
|
+
``for_storage=True`` produces the FROZEN v1 projection ``_extract`` persists
|
|
1181
|
+
(spec section 3.0): no ``exit_code``, no ``wall_time_seconds``, the status
|
|
1182
|
+
resolved only by ``_HARNESS_STATUS_RE``, and the preamble left in the part.
|
|
1183
|
+
Every reader path takes the default, which resolves the status from the five
|
|
1184
|
+
real grammars and removes the consumed lines from the rendered parts (spec
|
|
1185
|
+
sections 4.3 and 4.4).
|
|
1186
|
+
"""
|
|
788
1187
|
if not isinstance(payload, dict) or payload.get("type") not in _RESPONSE_TOOL_OUTPUTS:
|
|
789
1188
|
return None
|
|
790
1189
|
value = payload["output"] if "output" in payload else payload.get("tools")
|
|
@@ -792,23 +1191,41 @@ def decode_tool_output_card(
|
|
|
792
1191
|
budget = _TextBudget(text_cap)
|
|
793
1192
|
parts: list[dict] = []
|
|
794
1193
|
status = payload.get("status") if isinstance(payload.get("status"), str) else "unknown"
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
parts.append({"type": "text", "stream": "output", "text": budget.take(text)})
|
|
803
|
-
continue
|
|
804
|
-
if isinstance(part, dict) and part.get("type") == "input_text" \
|
|
805
|
-
and isinstance(part.get("text"), str):
|
|
806
|
-
text = part["text"]
|
|
807
|
-
match = _HARNESS_STATUS_RE.fullmatch(text) if index == 0 else None
|
|
1194
|
+
fields: dict = {}
|
|
1195
|
+
preamble = None
|
|
1196
|
+
drop_head = False
|
|
1197
|
+
head = _output_head_text(values[0]) if values else None
|
|
1198
|
+
if head is not None:
|
|
1199
|
+
if for_storage:
|
|
1200
|
+
match = _HARNESS_STATUS_RE.fullmatch(head)
|
|
808
1201
|
if match is not None:
|
|
809
|
-
status = "completed" if match.group(1) == "Script completed"
|
|
810
|
-
|
|
811
|
-
|
|
1202
|
+
status = ("completed" if match.group(1) == "Script completed"
|
|
1203
|
+
else "failed")
|
|
1204
|
+
drop_head = True
|
|
1205
|
+
else:
|
|
1206
|
+
preamble = parse_harness_preamble(head)
|
|
1207
|
+
if preamble is not None:
|
|
1208
|
+
fields = preamble[0]
|
|
1209
|
+
# A resolved state is evidence and wins. `unknown` adds nothing,
|
|
1210
|
+
# so it leaves `status` alone — which is what keeps the query
|
|
1211
|
+
# layer's unknown-gated backfill covering the remainder (4.6).
|
|
1212
|
+
if fields["status"] != "unknown":
|
|
1213
|
+
status = fields["status"]
|
|
1214
|
+
drop_head = not preamble[1]
|
|
1215
|
+
|
|
1216
|
+
for index, part in enumerate(values[:_CARD_MAX_PARTS]):
|
|
1217
|
+
if isinstance(part, str) or (
|
|
1218
|
+
isinstance(part, dict) and part.get("type") == "input_text"
|
|
1219
|
+
and isinstance(part.get("text"), str)):
|
|
1220
|
+
text = part if isinstance(part, str) else part["text"]
|
|
1221
|
+
if index == 0 and head is not None:
|
|
1222
|
+
if drop_head:
|
|
1223
|
+
continue
|
|
1224
|
+
if preamble is not None:
|
|
1225
|
+
text = preamble[1]
|
|
1226
|
+
stream = "output"
|
|
1227
|
+
if isinstance(part, dict) and part.get("stream") in {"stdout", "stderr"}:
|
|
1228
|
+
stream = part["stream"]
|
|
812
1229
|
parts.append({"type": "text", "stream": stream, "text": budget.take(text)})
|
|
813
1230
|
continue
|
|
814
1231
|
raw = _canonical_json(part)
|
|
@@ -821,50 +1238,373 @@ def decode_tool_output_card(
|
|
|
821
1238
|
"is_error": status in {"failed", "error"},
|
|
822
1239
|
"parts": parts, "truncated": budget.truncated,
|
|
823
1240
|
}
|
|
1241
|
+
if not for_storage:
|
|
1242
|
+
# Null when the grammar did not supply it. No `chunk_id` and no
|
|
1243
|
+
# `session_id`: a per-call hash has no reading value, and the session
|
|
1244
|
+
# identity a reader sees is the conversation-local ordinal from the
|
|
1245
|
+
# detail envelope's index, never the provider's own id (section 4.3).
|
|
1246
|
+
card["exit_code"] = fields.get("exit_code")
|
|
1247
|
+
card["wall_time_seconds"] = fields.get("wall_time_seconds")
|
|
824
1248
|
return card, "".join(part["text"] for part in parts)
|
|
825
1249
|
|
|
826
1250
|
|
|
827
|
-
|
|
828
|
-
|
|
1251
|
+
# The keys the dict-shaped `changes` entry is known to carry. Anything else goes
|
|
1252
|
+
# to `raw_extra`, so a provider addition is preserved rather than dropped.
|
|
1253
|
+
_PATCH_CHANGE_KEYS = frozenset({"type", "unified_diff", "content", "move_path"})
|
|
1254
|
+
|
|
1255
|
+
|
|
1256
|
+
def _synthesized_unified_diff(path: str, kind: str, content: str) -> str:
|
|
1257
|
+
"""A unified diff for an ``add`` or ``delete`` entry, from its `content`.
|
|
1258
|
+
|
|
1259
|
+
The bytes are specified exactly because ``NativePatchCard`` does not render
|
|
1260
|
+
prefixed lines — it requires a parseable hunk. An empty file emits a
|
|
1261
|
+
zero-length hunk rather than a malformed one, and content that does not end
|
|
1262
|
+
in a newline gets the ``\`` marker so the stated
|
|
1263
|
+
line count and the rendered result agree.
|
|
1264
|
+
|
|
1265
|
+
This is a rendering of retained content, not something the provider sent,
|
|
1266
|
+
which is why every entry built from it is marked ``diff_source: derived``.
|
|
1267
|
+
"""
|
|
1268
|
+
lines = content.split("\n")
|
|
1269
|
+
if lines and lines[-1] == "":
|
|
1270
|
+
lines.pop()
|
|
1271
|
+
complete = True
|
|
1272
|
+
else:
|
|
1273
|
+
complete = False
|
|
1274
|
+
count = len(lines)
|
|
1275
|
+
if kind == "add":
|
|
1276
|
+
header = f"--- /dev/null\n+++ {path}\n"
|
|
1277
|
+
hunk = "@@ -0,0 +0,0 @@\n" if count == 0 else f"@@ -0,0 +1,{count} @@\n"
|
|
1278
|
+
body = "".join(f"+{line}\n" for line in lines)
|
|
1279
|
+
else:
|
|
1280
|
+
header = f"--- {path}\n+++ /dev/null\n"
|
|
1281
|
+
hunk = "@@ -0,0 +0,0 @@\n" if count == 0 else f"@@ -1,{count} +0,0 @@\n"
|
|
1282
|
+
body = "".join(f"-{line}\n" for line in lines)
|
|
1283
|
+
marker = "" if complete or count == 0 else "\\n"
|
|
1284
|
+
return header + hunk + body + marker
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
def _clip_to_line_boundary(text: str, limit: int) -> tuple[str, bool]:
|
|
1288
|
+
"""``(text, was_cut)``, cut at a line boundary so the result still parses."""
|
|
1289
|
+
if len(text) <= limit:
|
|
1290
|
+
return text, False
|
|
1291
|
+
cut = text.rfind("\n", 0, limit)
|
|
1292
|
+
return (text[:cut + 1] if cut >= 0 else ""), True
|
|
1293
|
+
|
|
1294
|
+
|
|
1295
|
+
def _diff_body_survived(diff: str) -> bool:
|
|
1296
|
+
"""True iff ``diff`` still carries a line a hunk can render.
|
|
1297
|
+
|
|
1298
|
+
`has_diff` means a diff is RENDERABLE (spec section 3.1), and a clip that
|
|
1299
|
+
keeps only the `---`/`+++`/`@@` headers renders nothing at all — the very
|
|
1300
|
+
shape a minified single-line file produces, because the whole file body is
|
|
1301
|
+
one physical line and the clip cuts at a line boundary. The same clip can
|
|
1302
|
+
also keep nothing at all, when the first line is already longer than the
|
|
1303
|
+
share.
|
|
1304
|
+
|
|
1305
|
+
A diff that was NOT cut is never tested against this, so the deliberate
|
|
1306
|
+
zero-length hunk an empty file emits (`@@ -0,0 +0,0 @@` with no body, spec
|
|
1307
|
+
section 3.1) keeps its diff.
|
|
1308
|
+
"""
|
|
1309
|
+
seen_hunk = False
|
|
1310
|
+
for line in diff.split("\n"):
|
|
1311
|
+
if line.startswith("@@"):
|
|
1312
|
+
seen_hunk = True
|
|
1313
|
+
elif seen_hunk and line[:1] in ("+", "-", " ", "\\"):
|
|
1314
|
+
return True
|
|
1315
|
+
return False
|
|
1316
|
+
|
|
1317
|
+
|
|
1318
|
+
def _allocate_diff_budget(entries: list[dict], diffs: list[str | None],
|
|
1319
|
+
budget: _TextBudget) -> None:
|
|
1320
|
+
"""Divide what is left of the shared budget equally among the files.
|
|
1321
|
+
|
|
1322
|
+
The single 16,000-character budget covers every file plus stdout and stderr,
|
|
1323
|
+
so a per-file cap is an ALLOCATION of that budget rather than an addition to
|
|
1324
|
+
it. A file needing less returns the remainder to the pool for later files; a
|
|
1325
|
+
file exceeding its share is cut at a line boundary and sets its own
|
|
1326
|
+
`truncated`. This is what stops the average 20,859-character deletion from
|
|
1327
|
+
consuming the budget belonging to the other files in the 487 events that
|
|
1328
|
+
touch two or more files.
|
|
1329
|
+
|
|
1330
|
+
A cut that leaves nothing renderable publishes NO ``unified_diff`` at all,
|
|
1331
|
+
only `truncated` and `diff_source`, so the client never receives a key it
|
|
1332
|
+
cannot render while `has_diff` claims it can.
|
|
1333
|
+
"""
|
|
1334
|
+
pending = sum(1 for diff in diffs if diff is not None)
|
|
1335
|
+
for entry, diff in zip(entries, diffs):
|
|
1336
|
+
if diff is None:
|
|
1337
|
+
continue
|
|
1338
|
+
share = budget.remaining // pending if pending else 0
|
|
1339
|
+
kept, was_cut = _clip_to_line_boundary(diff, share)
|
|
1340
|
+
pending -= 1
|
|
1341
|
+
entry["truncated"] = was_cut
|
|
1342
|
+
if was_cut:
|
|
1343
|
+
budget.truncated = True
|
|
1344
|
+
if not _diff_body_survived(kept):
|
|
1345
|
+
continue
|
|
1346
|
+
entry["unified_diff"] = budget.take(kept)
|
|
1347
|
+
|
|
1348
|
+
|
|
1349
|
+
def _patch_files_from_changes_map(changes: dict, budget: _TextBudget) -> list[dict]:
|
|
1350
|
+
"""The dict-shaped ``changes`` branch (spec section 3.1).
|
|
1351
|
+
|
|
1352
|
+
The path comes from the dict KEY and the status from the entry's ``type``,
|
|
1353
|
+
not from ``path``/``status`` — the two shapes disagree on both, which is why
|
|
1354
|
+
a direct port of the list loop would yield `(unknown file)` and a null status
|
|
1355
|
+
on every entry.
|
|
1356
|
+
"""
|
|
1357
|
+
entries: list[dict] = []
|
|
1358
|
+
diffs: list[str | None] = []
|
|
1359
|
+
for path, change in list(changes.items())[:_CARD_MAX_FILES]:
|
|
1360
|
+
if not isinstance(path, str):
|
|
1361
|
+
continue
|
|
1362
|
+
# Per-file `truncated` is a wire field on EVERY entry (spec section 3.1),
|
|
1363
|
+
# so it is set here rather than only where a diff exists. Without it a
|
|
1364
|
+
# client would have to treat absent as false on the entries that carry no
|
|
1365
|
+
# diff, which is not what the spec describes.
|
|
1366
|
+
entry: dict[str, Any] = {"path": budget.take(path), "truncated": False}
|
|
1367
|
+
if not isinstance(change, dict):
|
|
1368
|
+
entry["raw"] = budget.take(_canonical_json(change))
|
|
1369
|
+
entries.append(entry)
|
|
1370
|
+
diffs.append(None)
|
|
1371
|
+
continue
|
|
1372
|
+
kind = change.get("type")
|
|
1373
|
+
if isinstance(kind, str):
|
|
1374
|
+
entry["status"] = budget.take(kind)
|
|
1375
|
+
if isinstance(change.get("move_path"), str):
|
|
1376
|
+
entry["move_path"] = budget.take(change["move_path"])
|
|
1377
|
+
diff: str | None = None
|
|
1378
|
+
if isinstance(change.get("unified_diff"), str):
|
|
1379
|
+
diff = change["unified_diff"]
|
|
1380
|
+
entry["diff_source"] = "retained"
|
|
1381
|
+
elif kind in {"add", "delete"} and isinstance(change.get("content"), str):
|
|
1382
|
+
# The PROVIDER's path, never `entry["path"]`, which the shared budget
|
|
1383
|
+
# may have clipped. A `---`/`+++` header naming a truncated path
|
|
1384
|
+
# would make the one card family whose purpose is provider-truth
|
|
1385
|
+
# assert a file that does not exist; the synthesized diff is clipped
|
|
1386
|
+
# by the shared allocation below, exactly as a retained one is.
|
|
1387
|
+
diff = _synthesized_unified_diff(path, kind, change["content"])
|
|
1388
|
+
entry["diff_source"] = "derived"
|
|
1389
|
+
unknown = {key: value for key, value in change.items()
|
|
1390
|
+
if key not in _PATCH_CHANGE_KEYS}
|
|
1391
|
+
if unknown:
|
|
1392
|
+
entry["raw_extra"] = budget.take(_canonical_json(unknown))
|
|
1393
|
+
entries.append(entry)
|
|
1394
|
+
diffs.append(diff)
|
|
1395
|
+
_allocate_diff_budget(entries, diffs, budget)
|
|
1396
|
+
return entries
|
|
1397
|
+
|
|
1398
|
+
|
|
1399
|
+
def decode_patch_event_card(
|
|
1400
|
+
payload: dict, *, text_cap: int = CODEX_TEXT_CAP, for_storage: bool = False,
|
|
1401
|
+
) -> dict | None:
|
|
1402
|
+
"""Bounded, provider-truthful ``patch_apply_end`` projection.
|
|
1403
|
+
|
|
1404
|
+
``for_storage=True`` produces the FROZEN v1 projection ``_extract``
|
|
1405
|
+
persists (spec section 3.0): a dict-shaped ``changes`` object becomes one
|
|
1406
|
+
``{"raw": …}`` entry, as it does today. Persisting the decoded per-file
|
|
1407
|
+
diffs would be the largest `detail_json` growth in S3 and would move
|
|
1408
|
+
`PAGE_SOURCE_BYTE_BUDGET` boundaries for newly ingested rows only.
|
|
1409
|
+
"""
|
|
829
1410
|
if not isinstance(payload, dict) or payload.get("type") != "patch_apply_end":
|
|
830
1411
|
return None
|
|
831
1412
|
budget = _TextBudget(text_cap)
|
|
832
1413
|
files: list[dict] = []
|
|
833
1414
|
changes = payload.get("changes")
|
|
834
|
-
|
|
1415
|
+
stdout: str | None = None
|
|
1416
|
+
stderr: str | None = None
|
|
1417
|
+
streams_taken = False
|
|
1418
|
+
if not for_storage and isinstance(changes, dict):
|
|
1419
|
+
# stdout and stderr come off the shared budget FIRST here, because what
|
|
1420
|
+
# is left is what the per-file allocation divides.
|
|
1421
|
+
stdout = (budget.take(payload["stdout"])
|
|
1422
|
+
if isinstance(payload.get("stdout"), str) else None)
|
|
1423
|
+
stderr = (budget.take(payload["stderr"])
|
|
1424
|
+
if isinstance(payload.get("stderr"), str) else None)
|
|
1425
|
+
streams_taken = True
|
|
1426
|
+
files = _patch_files_from_changes_map(changes, budget)
|
|
1427
|
+
if len(changes) > _CARD_MAX_FILES:
|
|
1428
|
+
budget.truncated = True
|
|
1429
|
+
elif isinstance(changes, list):
|
|
835
1430
|
for change in changes[:_CARD_MAX_FILES]:
|
|
836
1431
|
if not isinstance(change, dict):
|
|
837
|
-
|
|
1432
|
+
blob = _canonical_json(change)
|
|
1433
|
+
entry = {"raw": budget.take(blob)}
|
|
1434
|
+
if not for_storage:
|
|
1435
|
+
entry["truncated"] = len(entry["raw"]) != len(blob)
|
|
1436
|
+
files.append(entry)
|
|
838
1437
|
continue
|
|
839
1438
|
entry: dict[str, Any] = {}
|
|
1439
|
+
entry_cut = False
|
|
840
1440
|
for key in ("path", "move_path", "status"):
|
|
841
1441
|
if isinstance(change.get(key), str):
|
|
842
1442
|
entry[key] = budget.take(change[key])
|
|
1443
|
+
entry_cut = entry_cut or len(entry[key]) != len(change[key])
|
|
843
1444
|
if isinstance(change.get("unified_diff"), str):
|
|
844
1445
|
entry["unified_diff"] = budget.take(change["unified_diff"])
|
|
1446
|
+
entry_cut = entry_cut or (
|
|
1447
|
+
len(entry["unified_diff"]) != len(change["unified_diff"]))
|
|
1448
|
+
# The provider transmitted this one; the card must not claim a
|
|
1449
|
+
# synthesized diff and a retained diff are the same thing.
|
|
1450
|
+
# Read time ONLY: this is an added field, and adding it to the
|
|
1451
|
+
# stored card would grow `detail_json` for newly ingested rows
|
|
1452
|
+
# while historical rows kept their old estimates, which is
|
|
1453
|
+
# precisely the page-boundary drift spec section 3.0 forbids.
|
|
1454
|
+
if not for_storage:
|
|
1455
|
+
entry["diff_source"] = "retained"
|
|
845
1456
|
unknown = {key: value for key, value in change.items()
|
|
846
1457
|
if key not in {"path", "move_path", "status", "unified_diff"}}
|
|
847
1458
|
if unknown:
|
|
848
|
-
|
|
1459
|
+
blob = _canonical_json(unknown)
|
|
1460
|
+
entry["raw_extra"] = budget.take(blob)
|
|
1461
|
+
entry_cut = entry_cut or len(entry["raw_extra"]) != len(blob)
|
|
1462
|
+
# Per-file `truncated` on EVERY entry (spec section 3.1), read time
|
|
1463
|
+
# only for the same reason `diff_source` is.
|
|
1464
|
+
if not for_storage:
|
|
1465
|
+
entry["truncated"] = entry_cut
|
|
849
1466
|
files.append(entry)
|
|
850
1467
|
if len(changes) > _CARD_MAX_FILES:
|
|
851
1468
|
budget.truncated = True
|
|
852
1469
|
elif "changes" in payload:
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
1470
|
+
blob = _canonical_json(changes)
|
|
1471
|
+
entry = {"raw": budget.take(blob)}
|
|
1472
|
+
if not for_storage:
|
|
1473
|
+
entry["truncated"] = len(entry["raw"]) != len(blob)
|
|
1474
|
+
files.append(entry)
|
|
1475
|
+
if not streams_taken:
|
|
1476
|
+
stdout = (budget.take(payload["stdout"])
|
|
1477
|
+
if isinstance(payload.get("stdout"), str) else None)
|
|
1478
|
+
stderr = (budget.take(payload["stderr"])
|
|
1479
|
+
if isinstance(payload.get("stderr"), str) else None)
|
|
856
1480
|
status = payload.get("status") if isinstance(payload.get("status"), str) else "unknown"
|
|
857
1481
|
success = payload.get("success") if isinstance(payload.get("success"), bool) else None
|
|
858
1482
|
return {
|
|
859
1483
|
"schema_version": CODEX_CARD_SCHEMA_VERSION,
|
|
860
1484
|
"type": "patch", "source": "patch_apply_end",
|
|
861
1485
|
"files": files,
|
|
862
|
-
|
|
1486
|
+
# RENDERABLE, not merely present (spec section 3.1). An entry whose clip
|
|
1487
|
+
# kept nothing a hunk can render publishes no `unified_diff` at all, and
|
|
1488
|
+
# a provider that transmits an empty one must not flip this to true.
|
|
1489
|
+
"has_diff": any(entry.get("unified_diff") for entry in files),
|
|
863
1490
|
"status": status, "success": success, "stdout": stdout, "stderr": stderr,
|
|
864
1491
|
"truncated": budget.truncated,
|
|
865
1492
|
}
|
|
866
1493
|
|
|
867
1494
|
|
|
1495
|
+
# ── the external-agent marker (F9, spec section 5.5) ─────────────────────────
|
|
1496
|
+
#
|
|
1497
|
+
# Anchored at the start of a line. The name is 1-128 characters excluding `]` and
|
|
1498
|
+
# newline; the next line must open with `input: ` and carry a JSON value.
|
|
1499
|
+
# Validation is all-or-nothing: a name that does not match, an absent `input:`
|
|
1500
|
+
# line, or JSON that does not parse leaves the block as ordinary prose.
|
|
1501
|
+
_EXTERNAL_CALL_RE = re.compile(
|
|
1502
|
+
r"^\[external_agent_tool_call: ([^\]\n]{1,128})\]\n", re.MULTILINE)
|
|
1503
|
+
_EXTERNAL_CALL_INPUT = "input: "
|
|
1504
|
+
|
|
1505
|
+
|
|
1506
|
+
def _inside_fenced_block(text: str, position: int) -> bool:
|
|
1507
|
+
"""True when ``position`` falls inside an open Markdown fence.
|
|
1508
|
+
|
|
1509
|
+
A marker inside a fenced code block is authored content — someone writing
|
|
1510
|
+
ABOUT the grammar — rather than a serialized call, so it must stay prose.
|
|
1511
|
+
Same toggle rule `_segment_harness_markers` already uses.
|
|
1512
|
+
"""
|
|
1513
|
+
fence: str | None = None
|
|
1514
|
+
for line in text[:position].splitlines():
|
|
1515
|
+
opened = re.match(r"\s*(`{3,}|~{3,})", line)
|
|
1516
|
+
if opened is None:
|
|
1517
|
+
continue
|
|
1518
|
+
char = opened.group(1)[0]
|
|
1519
|
+
if fence is None:
|
|
1520
|
+
fence = char
|
|
1521
|
+
elif fence == char:
|
|
1522
|
+
fence = None
|
|
1523
|
+
return fence is not None
|
|
1524
|
+
|
|
1525
|
+
|
|
1526
|
+
def _external_call_from_text(
|
|
1527
|
+
text: str, *, text_cap: int = CODEX_TEXT_CAP,
|
|
1528
|
+
) -> dict | None:
|
|
1529
|
+
"""The `external_agent_tool_call` marker on an assistant row, or ``None``.
|
|
1530
|
+
|
|
1531
|
+
This runs at READ time over the row's stored text and needs no payload load,
|
|
1532
|
+
which is the only way it can reach the data it targets: all 3,789 markers are
|
|
1533
|
+
historical, dated 2026-07-11 across 46 conversations, and the grammar has not
|
|
1534
|
+
recurred — so an ingest-time implementation would detect nothing at all and
|
|
1535
|
+
would ship as a no-op against the only rows it exists for.
|
|
1536
|
+
|
|
1537
|
+
The caller writes this to ``detail.external_call`` and must NEVER write it to
|
|
1538
|
+
``detail.markers``, because that key selects which rows the export path
|
|
1539
|
+
hydrates and 729 assistant rows already carry it.
|
|
1540
|
+
|
|
1541
|
+
``span`` is the half-open ``[start, end)`` character range of the marker run
|
|
1542
|
+
the card consumed, measured in the SAME string this was handed — which is the
|
|
1543
|
+
string the caller serves as ``block["text"]``. The export keeps that prose
|
|
1544
|
+
verbatim and its bytes are frozen, so the client has no other way to locate
|
|
1545
|
+
what the card already renders, and without the span the viewer would show the
|
|
1546
|
+
marker twice. The range covers the ``[external_agent_tool_call: …]`` line,
|
|
1547
|
+
the ``input:`` line and the JSON value, plus one immediately following
|
|
1548
|
+
newline when there is one, so removing it leaves no orphaned blank line.
|
|
1549
|
+
"""
|
|
1550
|
+
if not isinstance(text, str) or not text:
|
|
1551
|
+
return None
|
|
1552
|
+
if len(text) > _CARD_HARNESS_PARSE_CAP:
|
|
1553
|
+
return None
|
|
1554
|
+
match = _EXTERNAL_CALL_RE.search(text)
|
|
1555
|
+
if match is None:
|
|
1556
|
+
return None
|
|
1557
|
+
if _inside_fenced_block(text, match.start()):
|
|
1558
|
+
return None
|
|
1559
|
+
rest = text[match.end():]
|
|
1560
|
+
if not rest.startswith(_EXTERNAL_CALL_INPUT):
|
|
1561
|
+
return None
|
|
1562
|
+
try:
|
|
1563
|
+
value, end = json.JSONDecoder().raw_decode(rest, len(_EXTERNAL_CALL_INPUT))
|
|
1564
|
+
except (json.JSONDecodeError, TypeError, ValueError):
|
|
1565
|
+
return None
|
|
1566
|
+
start = match.start()
|
|
1567
|
+
end += match.end()
|
|
1568
|
+
if end < len(text) and text[end] == "\n":
|
|
1569
|
+
end += 1
|
|
1570
|
+
if not 0 <= start < end <= len(text):
|
|
1571
|
+
# Fail closed. A span that does not resolve is worse than no card: the
|
|
1572
|
+
# client would hide the wrong run of text, or none at all.
|
|
1573
|
+
return None
|
|
1574
|
+
budget = _TextBudget(text_cap)
|
|
1575
|
+
return {
|
|
1576
|
+
"schema_version": CODEX_CARD_SCHEMA_VERSION,
|
|
1577
|
+
"name": budget.take(match.group(1)),
|
|
1578
|
+
"input": _bounded_json(value, budget),
|
|
1579
|
+
"truncated": budget.truncated,
|
|
1580
|
+
"span": [start, end],
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
|
|
1584
|
+
def external_call_span_resolves(text: Any, external: Any) -> bool:
|
|
1585
|
+
"""True iff ``external``'s span addresses its own marker inside ``text``.
|
|
1586
|
+
|
|
1587
|
+
The assembler calls this against the exact string it is about to serve as
|
|
1588
|
+
``block["text"]``, and withholds the card when it returns False. That is the
|
|
1589
|
+
fail-closed half of the span contract: a marker the served text does not
|
|
1590
|
+
wholly contain — because the row's text was capped at ingest, or because a
|
|
1591
|
+
later stage replaced it — yields no ``external_call`` at all rather than a
|
|
1592
|
+
span pointing at the wrong characters.
|
|
1593
|
+
"""
|
|
1594
|
+
if not isinstance(text, str) or not isinstance(external, dict):
|
|
1595
|
+
return False
|
|
1596
|
+
span = external.get("span")
|
|
1597
|
+
if not isinstance(span, list) or len(span) != 2:
|
|
1598
|
+
return False
|
|
1599
|
+
start, end = span
|
|
1600
|
+
for bound in (start, end):
|
|
1601
|
+
if not isinstance(bound, int) or isinstance(bound, bool):
|
|
1602
|
+
return False
|
|
1603
|
+
if not 0 <= start < end <= len(text):
|
|
1604
|
+
return False
|
|
1605
|
+
return text[start:end].startswith("[external_agent_tool_call: ")
|
|
1606
|
+
|
|
1607
|
+
|
|
868
1608
|
@dataclasses.dataclass
|
|
869
1609
|
class _Extracted:
|
|
870
1610
|
kind: str
|
|
@@ -1242,15 +1982,22 @@ def _extract(record_type: str | None, payload: dict) -> _Extracted | None:
|
|
|
1242
1982
|
args = _stringify(payload.get("input") or payload.get("arguments"))
|
|
1243
1983
|
text = f"{name}\n{args}" if args else str(name)
|
|
1244
1984
|
detail = {"name": name, "args": args[:CODEX_TEXT_CAP]}
|
|
1245
|
-
|
|
1985
|
+
# Frozen v1 projection at ingest (spec section 3.0) — see the
|
|
1986
|
+
# `for_storage` note on `decode_tool_output_card` above.
|
|
1987
|
+
card = decode_tool_call_card(payload, for_storage=True)
|
|
1246
1988
|
if card is None:
|
|
1247
|
-
card = decode_secondary_tool_call_card(payload)
|
|
1989
|
+
card = decode_secondary_tool_call_card(payload, for_storage=True)
|
|
1248
1990
|
if card is not None:
|
|
1249
1991
|
detail["card"] = card
|
|
1250
1992
|
return _Extracted("tool_call", text, "search_tool", detail, [])
|
|
1251
1993
|
if ptype in _RESPONSE_TOOL_OUTPUTS:
|
|
1252
1994
|
value = payload["output"] if "output" in payload else payload.get("tools")
|
|
1253
|
-
|
|
1995
|
+
# `for_storage=True`: the stored card is the FROZEN v1 projection
|
|
1996
|
+
# (spec section 3.0). Every S3 enrichment is computed at read time,
|
|
1997
|
+
# because `detail_json` bytes feed `_row_source_bytes` and therefore
|
|
1998
|
+
# page boundaries, and a richer stored card would paginate a newly
|
|
1999
|
+
# ingested conversation differently from an identical historical one.
|
|
2000
|
+
shaped = decode_tool_output_card(payload, for_storage=True)
|
|
1254
2001
|
if shaped is not None:
|
|
1255
2002
|
card, _display_body = shaped
|
|
1256
2003
|
return _Extracted(
|
|
@@ -1287,7 +2034,9 @@ def _extract(record_type: str | None, payload: dict) -> _Extracted | None:
|
|
|
1287
2034
|
detail = {"event": ptype}
|
|
1288
2035
|
if ptype in {"task_started", "task_complete"}:
|
|
1289
2036
|
detail.update(_lifecycle_projection(payload))
|
|
1290
|
-
|
|
2037
|
+
# Frozen v1 projection at ingest (spec section 3.0) — see the
|
|
2038
|
+
# `for_storage` note on `decode_tool_output_card` above.
|
|
2039
|
+
patch_card = decode_patch_event_card(payload, for_storage=True)
|
|
1291
2040
|
card = patch_card or decode_secondary_event_card(payload)
|
|
1292
2041
|
if card is not None:
|
|
1293
2042
|
detail["card"] = card
|
|
@@ -1304,13 +2053,8 @@ def _event_card(ptype: str, payload: dict) -> tuple[str, list[tuple[str, str]]]:
|
|
|
1304
2053
|
text = " ".join(p for p in ("task_complete", _stringify(
|
|
1305
2054
|
payload.get("last_agent_message"))) if p)
|
|
1306
2055
|
elif ptype == "patch_apply_end":
|
|
1307
|
-
paths =
|
|
1308
|
-
|
|
1309
|
-
if isinstance(changes, list):
|
|
1310
|
-
for change in changes:
|
|
1311
|
-
if isinstance(change, dict) and isinstance(change.get("path"), str):
|
|
1312
|
-
paths.append(change["path"])
|
|
1313
|
-
touches.append((change["path"], "apply_patch"))
|
|
2056
|
+
paths = codex_patch_file_paths(payload)
|
|
2057
|
+
touches.extend((path, "apply_patch") for path in paths)
|
|
1314
2058
|
text = " ".join(["patch_apply", *paths]) if paths else "patch_apply"
|
|
1315
2059
|
elif ptype == "mcp_tool_call_end":
|
|
1316
2060
|
invocation = payload.get("invocation")
|
|
@@ -1328,6 +2072,32 @@ def _event_card(ptype: str, payload: dict) -> tuple[str, list[tuple[str, str]]]:
|
|
|
1328
2072
|
return text, touches
|
|
1329
2073
|
|
|
1330
2074
|
|
|
2075
|
+
def codex_patch_file_paths(payload: Any) -> list[str]:
|
|
2076
|
+
"""Return provider-ordered file paths from one patch completion.
|
|
2077
|
+
|
|
2078
|
+
Current Codex emits ``changes`` as an object keyed by path; the legacy list
|
|
2079
|
+
shape carries the path on each entry. Only object-valued change entries are
|
|
2080
|
+
touches in either shape, matching the read-time patch derivation contract.
|
|
2081
|
+
"""
|
|
2082
|
+
if not isinstance(payload, dict) or payload.get("type") != "patch_apply_end":
|
|
2083
|
+
return []
|
|
2084
|
+
changes = payload.get("changes")
|
|
2085
|
+
if isinstance(changes, dict):
|
|
2086
|
+
items = changes.items()
|
|
2087
|
+
elif isinstance(changes, list):
|
|
2088
|
+
items = (
|
|
2089
|
+
(change.get("path"), change)
|
|
2090
|
+
for change in changes
|
|
2091
|
+
if isinstance(change, dict)
|
|
2092
|
+
)
|
|
2093
|
+
else:
|
|
2094
|
+
return []
|
|
2095
|
+
return [
|
|
2096
|
+
path for path, change in items
|
|
2097
|
+
if isinstance(path, str) and isinstance(change, dict)
|
|
2098
|
+
]
|
|
2099
|
+
|
|
2100
|
+
|
|
1331
2101
|
def infer_codex_event_turns(
|
|
1332
2102
|
events: Iterable[Any], *, initial_turn: str | None = None
|
|
1333
2103
|
) -> tuple[list[str | None], str | None]:
|