@piagent/platform 1.5.5 → 1.6.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.
Files changed (227) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/README.md +9 -8
  3. package/README.vi.md +9 -5
  4. package/adapters/web-frontend/profile.json +2 -4
  5. package/architecture/layers.json +18 -3
  6. package/benchmarks/deep-logic-v1/SPECIALIST_PROTOCOL.vi.md +122 -0
  7. package/benchmarks/deep-logic-v1/grade.mjs +272 -10
  8. package/benchmarks/deep-logic-v1/project/apps/admin/src/billing-summary.js +3 -0
  9. package/benchmarks/deep-logic-v1/project/benchmark-contract/public-smoke.test.js +166 -0
  10. package/benchmarks/deep-logic-v1/project/package.json +1 -1
  11. package/benchmarks/deep-logic-v1/project/packages/billing/src/close-period.js +3 -0
  12. package/benchmarks/deep-logic-v1/project/packages/billing/src/plan-timeline.js +3 -0
  13. package/benchmarks/deep-logic-v1/project/test/smoke.test.js +7 -1
  14. package/benchmarks/deep-logic-v1/prompts/budgeted-context-graph.md +2 -0
  15. package/benchmarks/deep-logic-v1/prompts/fair-dependency-scheduler.md +2 -0
  16. package/benchmarks/deep-logic-v1/prompts/layered-policy-resolution.md +2 -0
  17. package/benchmarks/deep-logic-v1/prompts/resumable-stream-assembly.md +3 -1
  18. package/benchmarks/deep-logic-v1/prompts/revision-event-reconciliation.md +2 -0
  19. package/benchmarks/deep-logic-v1/prompts/temporal-usage-billing-close.md +123 -0
  20. package/benchmarks/deep-logic-v1/prompts/transactional-config-merge.md +2 -0
  21. package/benchmarks/deep-logic-v1/suite.json +51 -3
  22. package/benchmarks/deep-logic-v1/variant.mjs +22 -1
  23. package/benchmarks/production-v1/spend-control.v1.json +68 -0
  24. package/benchmarks/production-v1/suite.json +42 -3
  25. package/catalog/capabilities.json +6 -6
  26. package/docs/command-reference-vietnamese.md +6 -6
  27. package/docs/context-engine.md +7 -1
  28. package/docs/distribution-standard.md +7 -7
  29. package/docs/en/architecture.md +1 -0
  30. package/docs/languages.json +4 -4
  31. package/docs/model-options.md +8 -5
  32. package/docs/operator-manual-vietnamese.md +13 -11
  33. package/docs/project-adapters.md +2 -2
  34. package/docs/publishing-for-teams.md +5 -0
  35. package/docs/quality-benchmark.md +271 -62
  36. package/docs/release-install-policy.md +10 -9
  37. package/docs/runtime-policy-design.md +4 -4
  38. package/docs/runtime-quality-baseline.md +13 -6
  39. package/docs/security-threat-model.md +2 -2
  40. package/docs/task-lifecycle-vietnamese.md +2 -2
  41. package/docs/team-onboarding.md +2 -2
  42. package/docs/usage-observability.md +7 -2
  43. package/docs/vi/architecture.md +1 -0
  44. package/docs-site/architecture.html +1 -1
  45. package/docs-site/benchmark.html +1 -1
  46. package/docs-site/commands.html +1 -1
  47. package/docs-site/context-engine.html +1 -1
  48. package/docs-site/en/architecture.html +1 -1
  49. package/docs-site/en/benchmark.html +1 -1
  50. package/docs-site/en/commands.html +1 -1
  51. package/docs-site/en/context-engine.html +1 -1
  52. package/docs-site/en/index.html +1 -1
  53. package/docs-site/en/mcp-approval.html +1 -1
  54. package/docs-site/en/mcp-auth.html +1 -1
  55. package/docs-site/en/mcp-servers.html +1 -1
  56. package/docs-site/en/mcp.html +1 -1
  57. package/docs-site/en/permissions.html +1 -1
  58. package/docs-site/en/profiles.html +1 -1
  59. package/docs-site/en/quickstart.html +1 -1
  60. package/docs-site/en/runtime.html +1 -1
  61. package/docs-site/en/scope.html +1 -1
  62. package/docs-site/en/security.html +1 -1
  63. package/docs-site/en/team.html +1 -1
  64. package/docs-site/en/whats-new.html +1 -1
  65. package/docs-site/en/workflows.html +1 -1
  66. package/docs-site/index.html +1 -1
  67. package/docs-site/mcp-approval.html +1 -1
  68. package/docs-site/mcp-auth.html +1 -1
  69. package/docs-site/mcp-servers.html +1 -1
  70. package/docs-site/mcp.html +1 -1
  71. package/docs-site/permissions.html +1 -1
  72. package/docs-site/profiles.html +1 -1
  73. package/docs-site/quickstart.html +1 -1
  74. package/docs-site/runtime.html +1 -1
  75. package/docs-site/scope.html +1 -1
  76. package/docs-site/security.html +1 -1
  77. package/docs-site/team.html +1 -1
  78. package/docs-site/whats-new.html +1 -1
  79. package/docs-site/workflows.html +1 -1
  80. package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
  81. package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
  82. package/evals/fixtures/piagent-webui/session-live-state-v1.invalid.json +18 -0
  83. package/evals/fixtures/piagent-webui/session-live-state-v1.valid.json +27 -0
  84. package/evals/fs4-readiness-matrix.v1.json +2 -2
  85. package/evals/fs5-causal-arm.v1.json +2 -2
  86. package/evals/fs5-pilot-protocol.v1.json +2 -2
  87. package/evals/fs5-pilot-protocol.v4.json +6 -5
  88. package/evals/fs5-pilot-protocol.v5.json +7 -6
  89. package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
  90. package/package.json +3 -1
  91. package/packages/piagent-core/benchmark/benchmark-assurance.js +4 -1
  92. package/packages/piagent-core/benchmark/benchmark-bootstrap.js +91 -9
  93. package/packages/piagent-core/benchmark/benchmark-claim-restrictions.js +38 -7
  94. package/packages/piagent-core/benchmark/benchmark-cli.js +7 -7
  95. package/packages/piagent-core/benchmark/benchmark-codex.js +3 -1
  96. package/packages/piagent-core/benchmark/benchmark-comparison.js +271 -2
  97. package/packages/piagent-core/benchmark/benchmark-core.js +732 -661
  98. package/packages/piagent-core/benchmark/benchmark-execution-guard.js +7 -5
  99. package/packages/piagent-core/benchmark/benchmark-forensics.js +439 -4
  100. package/packages/piagent-core/benchmark/benchmark-host-readiness.js +639 -0
  101. package/packages/piagent-core/benchmark/benchmark-matrix.js +22 -4
  102. package/packages/piagent-core/benchmark/benchmark-normalized-cost.js +317 -0
  103. package/packages/piagent-core/benchmark/benchmark-preflight.js +3 -1
  104. package/packages/piagent-core/benchmark/benchmark-process.js +6 -5
  105. package/packages/piagent-core/benchmark/benchmark-provider-wire.js +126 -0
  106. package/packages/piagent-core/benchmark/benchmark-record-validation.js +183 -0
  107. package/packages/piagent-core/benchmark/benchmark-report.js +116 -12
  108. package/packages/piagent-core/benchmark/benchmark-resume-recovery.js +14 -0
  109. package/packages/piagent-core/benchmark/benchmark-runner-policy.js +26 -0
  110. package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +782 -0
  111. package/packages/piagent-core/benchmark/benchmark-statistics.js +64 -0
  112. package/packages/piagent-core/benchmark/benchmark-stop-policy.js +8 -5
  113. package/packages/piagent-core/benchmark/benchmark-suite-identity.js +31 -0
  114. package/packages/piagent-core/benchmark/benchmark-suite-runtime.js +13 -9
  115. package/packages/piagent-core/benchmark/benchmark-suite.js +128 -2
  116. package/packages/piagent-core/benchmark/benchmark-summary-support.js +376 -0
  117. package/packages/piagent-core/benchmark/benchmark-timing-diagnostics.js +971 -0
  118. package/packages/piagent-core/benchmark/benchmark-usage.js +311 -0
  119. package/packages/piagent-core/benchmark/benchmark-workflow.js +166 -0
  120. package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +1080 -0
  121. package/packages/piagent-core/extensions/acceptance-contract-semantics.js +83 -69
  122. package/packages/piagent-core/extensions/acceptance-error-classes.js +160 -0
  123. package/packages/piagent-core/extensions/acceptance-executable-evidence.js +12 -9
  124. package/packages/piagent-core/extensions/acceptance-receipt.js +34 -11
  125. package/packages/piagent-core/extensions/acceptance-state-machine-evidence.js +518 -0
  126. package/packages/piagent-core/extensions/acceptance-test-binding-evidence.js +60 -0
  127. package/packages/piagent-core/extensions/context-efficiency-metrics.js +302 -0
  128. package/packages/piagent-core/extensions/context-efficiency-report.js +167 -0
  129. package/packages/piagent-core/extensions/context-engine.js +148 -145
  130. package/packages/piagent-core/extensions/context-evidence.js +48 -0
  131. package/packages/piagent-core/extensions/context-import-links.js +296 -0
  132. package/packages/piagent-core/extensions/context-telemetry-integrity.js +65 -0
  133. package/packages/piagent-core/extensions/criterion-context-pack.js +422 -7
  134. package/packages/piagent-core/extensions/criterion-graph.js +78 -8
  135. package/packages/piagent-core/extensions/guard-types.ts +2 -0
  136. package/packages/piagent-core/extensions/javascript-regex-evidence.js +32 -0
  137. package/packages/piagent-core/extensions/piagent-guard.ts +233 -41
  138. package/packages/piagent-core/extensions/task-contract-view.js +17 -3
  139. package/packages/piagent-core/extensions/task-lifecycle.js +7 -2
  140. package/packages/piagent-core/extensions/task-state.js +40 -134
  141. package/packages/piagent-core/extensions/verification-intelligence.js +3 -1
  142. package/packages/piagent-core/extensions/workspace-evidence-roots.js +157 -0
  143. package/packages/piagent-core/package.json +1 -1
  144. package/packages/piagent-core/policies/base-policy.json +2 -0
  145. package/packages/piagent-core/runtime/context/context-delivery.ts +103 -0
  146. package/packages/piagent-core/runtime/context/context-delta-shadow.ts +53 -0
  147. package/packages/piagent-core/runtime/context/context-efficiency-format.ts +47 -0
  148. package/packages/piagent-core/runtime/context/context-evidence-qualification.ts +32 -0
  149. package/packages/piagent-core/runtime/context/prefix-telemetry.ts +47 -0
  150. package/packages/piagent-core/runtime/context/repository-memory-hints.ts +27 -0
  151. package/packages/piagent-core/runtime/hooks/agent-prompt-telemetry.ts +56 -0
  152. package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +162 -102
  153. package/packages/piagent-core/runtime/hooks/completion-hook.ts +5 -6
  154. package/packages/piagent-core/runtime/hooks/input-hook.ts +11 -1
  155. package/packages/piagent-core/runtime/hooks/session-hooks.ts +22 -2
  156. package/packages/piagent-core/runtime/hooks/session-start-hook.ts +7 -2
  157. package/packages/piagent-core/runtime/hooks/tool-result-content-guards.ts +95 -0
  158. package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +84 -102
  159. package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +4 -2
  160. package/packages/piagent-core/runtime/inspection/activity-recovery.ts +104 -0
  161. package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +1 -1
  162. package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +1 -1
  163. package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +53 -0
  164. package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +34 -6
  165. package/packages/piagent-core/runtime/model/authenticated-catalog.ts +17 -9
  166. package/packages/piagent-core/runtime/model/openai-codex-reasoning.ts +83 -0
  167. package/packages/piagent-core/runtime/model/provider-wire-fingerprint.ts +238 -0
  168. package/packages/piagent-core/runtime/orchestration/role-policy.ts +1 -1
  169. package/packages/piagent-core/runtime/product/activity-inspector.ts +7 -2
  170. package/packages/piagent-core/runtime/product/efficiency-metrics.ts +4 -2
  171. package/packages/piagent-core/runtime/quality/exact-output-contract.ts +2 -1
  172. package/packages/piagent-core/runtime/quality/performance-assurance-signals.ts +23 -0
  173. package/packages/piagent-core/runtime/quality/performance-assurance.ts +6 -2
  174. package/packages/piagent-core/runtime/recovery/edit-recovery-context.ts +113 -0
  175. package/packages/piagent-core/runtime/recovery/edit-recovery-delivery.ts +42 -0
  176. package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +55 -2
  177. package/packages/piagent-core/runtime/recovery/semantic-repair-runtime.ts +55 -2
  178. package/packages/piagent-core/runtime/registration/context-commands.ts +66 -5
  179. package/packages/piagent-core/runtime/registration/policy-tools.ts +46 -11
  180. package/packages/piagent-core/runtime/registration/task-completion-tools.ts +49 -16
  181. package/packages/piagent-core/runtime/registration/task-start-guidance.ts +163 -0
  182. package/packages/piagent-core/runtime/registration/task-start-retry-helpers.ts +37 -0
  183. package/packages/piagent-core/runtime/registration/task-start-tool.ts +67 -62
  184. package/packages/piagent-core/runtime/session/runtime-state.ts +154 -2
  185. package/packages/piagent-core/runtime/session/system-prompt.ts +4 -4
  186. package/packages/piagent-core/runtime/tools/apply-patch-tool.ts +500 -0
  187. package/packages/piagent-core/runtime/trajectory/trajectory-runtime.ts +2 -1
  188. package/packages/piagent-core/runtime/workflows/task-intake.ts +43 -2
  189. package/packages/piagent-core/security/sensitive-data.js +13 -218
  190. package/packages/piagent-core/security/sensitive-project-file.js +42 -0
  191. package/packages/piagent-core/security/sensitive-source-expression.js +330 -0
  192. package/packages/piagent-core/security/sensitive-source-formats.js +394 -0
  193. package/packages/piagent-core/security/sensitive-text.js +234 -0
  194. package/packages/piagent-core/subagents/piagent-worker.md +1 -1
  195. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-rD9WJKxs.js → MarkdownMessage-CxeZqTte.js} +1 -1
  196. package/packages/piagent-webui/dist/client/assets/index-DynIMe8I.js +176 -0
  197. package/packages/piagent-webui/dist/client/index.html +1 -1
  198. package/packages/piagent-webui/gateway/gateway-events.ts +109 -5
  199. package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
  200. package/packages/piagent-webui/gateway/gateway-session-stream.ts +93 -7
  201. package/packages/piagent-webui/gateway/session-command-controller.ts +18 -3
  202. package/packages/piagent-webui/gateway/session-effective-options.ts +47 -0
  203. package/packages/piagent-webui/gateway/session-live-state.ts +21 -0
  204. package/packages/piagent-webui/gateway/session-operation-start.ts +5 -1
  205. package/packages/piagent-webui/gateway/session-operation-watchdog.ts +158 -0
  206. package/packages/piagent-webui/gateway/session-runtime-factory.ts +45 -0
  207. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +139 -124
  208. package/packages/piagent-webui/server/loopback-server.ts +8 -0
  209. package/packages/piagent-webui/server/transcript-projection.ts +22 -4
  210. package/packages/piagent-webui/shared/text-visibility.ts +10 -0
  211. package/schemas/piagent-webui/catalog-v1.json +9 -0
  212. package/schemas/piagent-webui/gateway-protocol-v1.schema.json +53 -3
  213. package/schemas/piagent-webui/session-live-state-v1.schema.json +76 -0
  214. package/schemas/task-contract.schema.json +12 -1
  215. package/scripts/benchmark-runner-core.mjs +355 -359
  216. package/scripts/benchmark-runner-finalization.mjs +309 -0
  217. package/scripts/benchmark-runner-host-readiness.mjs +94 -0
  218. package/scripts/benchmark-runner-support.mjs +200 -0
  219. package/scripts/benchmark-session.mjs +95 -41
  220. package/scripts/configure-model-scope.sh +4 -3
  221. package/scripts/helper-evaluation.mjs +1 -1
  222. package/scripts/install-global.sh +2 -2
  223. package/scripts/pi-usage-history.mjs +37 -10
  224. package/scripts/setup.sh +2 -2
  225. package/scripts/verify-local.sh +5 -1
  226. package/templates/global/settings.json +2 -1
  227. package/packages/piagent-webui/dist/client/assets/index-BZRFw1u6.js +0 -174
@@ -0,0 +1,971 @@
1
+ import { StringDecoder } from "node:string_decoder";
2
+
3
+ import { median, rounded } from "./benchmark-statistics.js";
4
+
5
+ const CODEX_MODEL_OUTPUT_ITEMS = new Set(["agent_message", "reasoning"]);
6
+ const CODEX_CONTROL_ITEMS = new Set(["todo_list"]);
7
+ const CODEX_COMPLETED_ONLY_CONTROL_ITEMS = new Set(["error"]);
8
+ const CODEX_COMPLETED_ONLY_TOOL_ITEMS = new Set(["file_change"]);
9
+ const CODEX_TIMED_TOOL_ITEMS = new Set(["command_execution", "mcp_tool_call", "collab_tool_call", "web_search"]);
10
+ const CODEX_COMMAND_TERMINAL_STATUSES = new Set(["completed", "failed", "declined"]);
11
+ const CODEX_TOOL_TERMINAL_STATUSES = new Set(["completed", "failed"]);
12
+ const CODEX_COLLAB_TOOLS = new Set(["spawn_agent", "send_input", "wait", "close_agent"]);
13
+ const CODEX_COLLAB_AGENT_STATUSES = new Set([
14
+ "pending_init", "running", "interrupted", "completed", "errored", "shutdown", "not_found"
15
+ ]);
16
+ const CODEX_USAGE_KEYS = ["input_tokens", "cached_input_tokens", "output_tokens", "reasoning_output_tokens"];
17
+ const PI_CONTROL_EVENTS = new Set([
18
+ "queue_update", "compaction_start", "entry_appended", "session_info_changed",
19
+ "thinking_level_changed", "compaction_end", "auto_retry_start", "auto_retry_end",
20
+ "summarization_retry_scheduled", "summarization_retry_attempt_start", "summarization_retry_finished",
21
+ "bash_execution_update"
22
+ ]);
23
+ const PI_MESSAGE_ROLES = new Set(["user", "assistant", "toolResult", "custom"]);
24
+ const SURFACES = new Set(["raw-pi", "piagent", "codex-cli"]);
25
+ const PHASES = ["processStartup", "modelTurnWait", "toolExecution", "other"];
26
+ const MAX_JSONL_BYTES = 64 * 1024 * 1024;
27
+ const MAX_TIMING_REPLAY_BYTES = 4 * 1024 * 1024;
28
+
29
+ function plainObject(value) {
30
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
31
+ }
32
+
33
+ function finiteNonnegative(value) {
34
+ return Number.isFinite(value) && value >= 0;
35
+ }
36
+
37
+ function phase(status, seconds, reason, boundary) {
38
+ return { status, seconds: status === "available" ? rounded(seconds, 6) : null, reason, boundary };
39
+ }
40
+
41
+ function intervalDuration(intervals) {
42
+ if (intervals.length === 0) return 0;
43
+ const ordered = intervals.slice().sort((left, right) => left[0] - right[0] || left[1] - right[1]);
44
+ let total = 0;
45
+ let [start, end] = ordered[0];
46
+ for (const [nextStart, nextEnd] of ordered.slice(1)) {
47
+ if (nextStart <= end) end = Math.max(end, nextEnd);
48
+ else { total += end - start; start = nextStart; end = nextEnd; }
49
+ }
50
+ return total + end - start;
51
+ }
52
+
53
+ function addInterval(state, target, start, end) {
54
+ if (!finiteNonnegative(start) || !finiteNonnegative(end) || end < start) {
55
+ state.invalidClockBoundaries += 1;
56
+ return false;
57
+ }
58
+ if (end === start) {
59
+ state.coalescedBoundaries += 1;
60
+ return false;
61
+ }
62
+ target.push([start, end]);
63
+ return true;
64
+ }
65
+
66
+ function safeId(value) {
67
+ return typeof value === "string" && value.length > 0 && value.length <= 1_000 ? value : null;
68
+ }
69
+
70
+ function sameIdentityMaps(left, right) {
71
+ if (left.size !== right.size) return false;
72
+ for (const [id, name] of left) if (right.get(id) !== name) return false;
73
+ return true;
74
+ }
75
+
76
+ function piToolResultIdentity(message) {
77
+ if (!plainObject(message) || message.role !== "toolResult") return null;
78
+ const id = safeId(message.toolCallId);
79
+ return id && typeof message.toolName === "string" && message.toolName ? { id, name: message.toolName } : null;
80
+ }
81
+
82
+ function validPiCustomMessage(message) {
83
+ return typeof message.customType === "string" && message.customType.length > 0
84
+ && (typeof message.content === "string" || Array.isArray(message.content))
85
+ && typeof message.display === "boolean" && finiteNonnegative(message.timestamp);
86
+ }
87
+
88
+ function invalidateEvent(state) {
89
+ state.malformedLines += 1;
90
+ }
91
+
92
+ function validPiControlEvent(event) {
93
+ if (event.type === "summarization_retry_finished") return true;
94
+ if (event.type === "queue_update") return Array.isArray(event.steering) && Array.isArray(event.followUp);
95
+ if (event.type === "compaction_start") return ["manual", "threshold", "overflow"].includes(event.reason);
96
+ if (event.type === "entry_appended") return plainObject(event.entry);
97
+ if (event.type === "session_info_changed") return event.name === undefined || typeof event.name === "string";
98
+ if (event.type === "thinking_level_changed") return typeof event.level === "string" && event.level.length > 0;
99
+ if (event.type === "compaction_end") return ["manual", "threshold", "overflow"].includes(event.reason)
100
+ && typeof event.aborted === "boolean" && typeof event.willRetry === "boolean"
101
+ && (event.result === undefined || event.result === null || plainObject(event.result));
102
+ if (event.type === "auto_retry_start" || event.type === "summarization_retry_scheduled") {
103
+ return Number.isSafeInteger(event.attempt) && event.attempt > 0
104
+ && Number.isSafeInteger(event.maxAttempts) && event.maxAttempts > 0
105
+ && finiteNonnegative(event.delayMs) && typeof event.errorMessage === "string";
106
+ }
107
+ if (event.type === "auto_retry_end") return typeof event.success === "boolean"
108
+ && Number.isSafeInteger(event.attempt) && event.attempt > 0
109
+ && (event.finalError === undefined || typeof event.finalError === "string");
110
+ if (event.type === "summarization_retry_attempt_start") return ["branchSummary", "compaction"].includes(event.source)
111
+ && (event.source !== "compaction" || ["manual", "threshold", "overflow"].includes(event.reason));
112
+ if (event.type === "bash_execution_update") return (event.id === undefined || safeId(event.id)) && typeof event.delta === "string";
113
+ return false;
114
+ }
115
+
116
+ function consumePiEvent(state, event, at) {
117
+ if (state.agentSettled) {
118
+ invalidateEvent(state);
119
+ return;
120
+ }
121
+ if (event.type === "session") {
122
+ if (state.jsonEvents !== 1 || state.sessionSeen || !Number.isSafeInteger(event.version) || event.version <= 0 || !safeId(event.id)
123
+ || typeof event.timestamp !== "string" || !event.timestamp || typeof event.cwd !== "string" || !event.cwd) invalidateEvent(state);
124
+ else {
125
+ state.sessionSeen = true;
126
+ state.sessionHeaders += 1;
127
+ }
128
+ return;
129
+ }
130
+ if (event.type === "agent_start") {
131
+ if (!state.sessionSeen || state.agentCycleOpen || state.turnOpen || state.modelTurnStart !== null
132
+ || state.openMessageRole !== null || state.openTools.size > 0) invalidateEvent(state);
133
+ else {
134
+ state.agentStarted = true;
135
+ state.agentCycleOpen = true;
136
+ state.agentEnded = false;
137
+ state.lastAgentEndWillRetry = null;
138
+ state.agentStarts += 1;
139
+ }
140
+ return;
141
+ }
142
+ if (event.type === "turn_start") {
143
+ if (!state.agentCycleOpen || state.turnOpen || state.modelTurnStart !== null) {
144
+ invalidateEvent(state);
145
+ return;
146
+ }
147
+ state.turnStarts += 1;
148
+ if (state.startupBoundary === null) state.startupBoundary = at;
149
+ state.turnOpen = true;
150
+ state.assistantSeenInTurn = false;
151
+ state.assistantMessageEnded = false;
152
+ state.turnToolStarts = 0;
153
+ state.turnToolCompletions = 0;
154
+ state.turnToolResultMessages = 0;
155
+ state.completedTurnTools.clear();
156
+ state.turnToolResultIdentities.clear();
157
+ state.openToolResultIdentity = null;
158
+ state.modelTurnStart = at;
159
+ return;
160
+ }
161
+ if (event.type === "message_start" || event.type === "message_end") {
162
+ if (!plainObject(event.message) || typeof event.message.role !== "string" || !state.turnOpen) {
163
+ invalidateEvent(state);
164
+ return;
165
+ }
166
+ const role = event.message.role;
167
+ if (!PI_MESSAGE_ROLES.has(role) || role === "custom" && !validPiCustomMessage(event.message)) { invalidateEvent(state); return; }
168
+ if (event.type === "message_start") {
169
+ if (state.openMessageRole !== null || (["user", "custom"].includes(role) && state.assistantSeenInTurn)
170
+ || (role === "toolResult" && (!state.assistantMessageEnded || state.openTools.size > 0))) {
171
+ invalidateEvent(state);
172
+ return;
173
+ }
174
+ if (role === "toolResult") {
175
+ const identity = piToolResultIdentity(event.message);
176
+ if (!identity || state.completedTurnTools.get(identity.id) !== identity.name
177
+ || state.turnToolResultIdentities.has(identity.id)) {
178
+ invalidateEvent(state);
179
+ return;
180
+ }
181
+ state.openToolResultIdentity = identity;
182
+ }
183
+ if (role === "custom") state.openCustomType = event.message.customType;
184
+ state.openMessageRole = role;
185
+ if (role !== "assistant") return;
186
+ if (state.assistantSeenInTurn || state.modelTurnStart === null) {
187
+ invalidateEvent(state);
188
+ return;
189
+ }
190
+ state.assistantSeenInTurn = true;
191
+ return;
192
+ }
193
+ if (state.openMessageRole !== role || role === "custom" && state.openCustomType !== event.message.customType) {
194
+ invalidateEvent(state);
195
+ return;
196
+ }
197
+ if (role === "toolResult") {
198
+ const identity = piToolResultIdentity(event.message);
199
+ if (!identity || state.openToolResultIdentity === null
200
+ || identity.id !== state.openToolResultIdentity.id || identity.name !== state.openToolResultIdentity.name) {
201
+ invalidateEvent(state);
202
+ return;
203
+ }
204
+ state.turnToolResultIdentities.set(identity.id, identity.name);
205
+ state.openToolResultIdentity = null;
206
+ }
207
+ state.openMessageRole = null;
208
+ if (role === "custom") state.openCustomType = null;
209
+ if (role === "user" || role === "custom") {
210
+ if (state.assistantSeenInTurn || state.modelTurnStart === null) invalidateEvent(state);
211
+ else state.modelTurnStart = at;
212
+ return;
213
+ }
214
+ if (role === "toolResult") {
215
+ if (!state.assistantMessageEnded || state.openTools.size > 0) invalidateEvent(state);
216
+ else state.turnToolResultMessages += 1;
217
+ }
218
+ if (role === "assistant") {
219
+ if (!state.assistantSeenInTurn || state.assistantMessageEnded || state.modelTurnStart === null) invalidateEvent(state);
220
+ else {
221
+ state.assistantBoundaries += 1;
222
+ if (addInterval(state, state.modelTurnIntervals, state.modelTurnStart, at)) state.matchedModelTurnIntervals += 1;
223
+ else state.modelTurnIntervalsInvalid = true;
224
+ state.modelTurnStart = null;
225
+ state.assistantMessageEnded = true;
226
+ }
227
+ }
228
+ return;
229
+ }
230
+ if (event.type === "message_update") {
231
+ if (!state.turnOpen || state.openMessageRole !== "assistant" || !plainObject(event.assistantMessageEvent)) invalidateEvent(state);
232
+ return;
233
+ }
234
+ if (event.type === "tool_execution_start") {
235
+ const id = safeId(event.toolCallId);
236
+ if (!state.turnOpen || !state.assistantMessageEnded || !id || typeof event.toolName !== "string" || !event.toolName
237
+ || state.openTools.has(id)) {
238
+ invalidateEvent(state);
239
+ return;
240
+ }
241
+ if (state.completedTurnTools.has(id)) {
242
+ invalidateEvent(state);
243
+ return;
244
+ }
245
+ state.toolStarts += 1;
246
+ state.turnToolStarts += 1;
247
+ state.openTools.set(id, { at, toolName: event.toolName });
248
+ return;
249
+ }
250
+ if (event.type === "tool_execution_end") {
251
+ const id = safeId(event.toolCallId);
252
+ if (!state.turnOpen || !state.assistantMessageEnded || !id || typeof event.toolName !== "string" || !event.toolName
253
+ || typeof event.isError !== "boolean") {
254
+ invalidateEvent(state);
255
+ return;
256
+ }
257
+ state.toolCompletions += 1;
258
+ state.turnToolCompletions += 1;
259
+ const started = state.openTools.get(id);
260
+ if (started === undefined || started.toolName !== event.toolName || state.completedTurnTools.has(id)) {
261
+ if (started !== undefined && started.toolName !== event.toolName) invalidateEvent(state);
262
+ state.unmatchedToolCompletions += 1;
263
+ }
264
+ else {
265
+ if (!addInterval(state, state.toolIntervals, started.at, at)) state.toolIntervalsInvalid = true;
266
+ state.matchedToolIntervals += 1;
267
+ state.openTools.delete(id);
268
+ state.completedTurnTools.set(id, event.toolName);
269
+ }
270
+ return;
271
+ }
272
+ if (event.type === "tool_execution_update") {
273
+ const id = safeId(event.toolCallId);
274
+ const started = id ? state.openTools.get(id) : undefined;
275
+ if (!state.turnOpen || !state.assistantMessageEnded || !id || started === undefined
276
+ || typeof event.toolName !== "string" || !event.toolName || started.toolName !== event.toolName) invalidateEvent(state);
277
+ return;
278
+ }
279
+ if (event.type === "turn_end") {
280
+ const receiptIdentities = new Map();
281
+ let receiptValid = Array.isArray(event.toolResults);
282
+ if (receiptValid) {
283
+ for (const result of event.toolResults) {
284
+ const identity = piToolResultIdentity(result);
285
+ if (!identity || receiptIdentities.has(identity.id)) { receiptValid = false; break; }
286
+ receiptIdentities.set(identity.id, identity.name);
287
+ }
288
+ }
289
+ if (!state.turnOpen || !state.assistantMessageEnded || !plainObject(event.message) || !Array.isArray(event.toolResults)
290
+ || event.message.role !== "assistant" || state.openMessageRole !== null
291
+ || state.openToolResultIdentity !== null || !receiptValid
292
+ || state.openTools.size > 0 || state.modelTurnStart !== null
293
+ || state.turnToolStarts !== state.turnToolCompletions
294
+ || state.turnToolCompletions !== state.turnToolResultMessages
295
+ || state.turnToolCompletions !== event.toolResults.length
296
+ || !sameIdentityMaps(state.completedTurnTools, state.turnToolResultIdentities)
297
+ || !sameIdentityMaps(state.completedTurnTools, receiptIdentities)) {
298
+ invalidateEvent(state);
299
+ return;
300
+ }
301
+ state.turnCompletions += 1;
302
+ state.turnOpen = false;
303
+ state.assistantSeenInTurn = false;
304
+ state.assistantMessageEnded = false;
305
+ state.turnToolStarts = 0;
306
+ state.turnToolCompletions = 0;
307
+ state.turnToolResultMessages = 0;
308
+ state.completedTurnTools.clear();
309
+ state.turnToolResultIdentities.clear();
310
+ state.openToolResultIdentity = null;
311
+ return;
312
+ }
313
+ if (event.type === "agent_end") {
314
+ if (!state.agentCycleOpen || typeof event.willRetry !== "boolean" || !Array.isArray(event.messages)
315
+ || state.turnOpen || state.modelTurnStart !== null || state.openMessageRole !== null || state.openTools.size > 0) invalidateEvent(state);
316
+ else {
317
+ state.agentCycleOpen = false;
318
+ state.agentEnded = true;
319
+ state.lastAgentEndWillRetry = event.willRetry;
320
+ state.agentEnds += 1;
321
+ }
322
+ return;
323
+ }
324
+ if (event.type === "agent_settled") {
325
+ if (!state.sessionSeen || !state.agentStarted || !state.agentEnded || state.agentCycleOpen
326
+ || state.lastAgentEndWillRetry !== false
327
+ || state.turnOpen || state.openMessageRole !== null || state.openTools.size > 0) invalidateEvent(state);
328
+ else {
329
+ state.agentSettled = true;
330
+ state.agentSettledEvents += 1;
331
+ }
332
+ return;
333
+ }
334
+ if (PI_CONTROL_EVENTS.has(event.type)) {
335
+ if (!validPiControlEvent(event)) invalidateEvent(state);
336
+ return;
337
+ }
338
+ invalidateEvent(state);
339
+ }
340
+
341
+ function codexItemKind(type) {
342
+ if (CODEX_MODEL_OUTPUT_ITEMS.has(type)) return "model-output";
343
+ if (CODEX_CONTROL_ITEMS.has(type)) return "control";
344
+ if (CODEX_COMPLETED_ONLY_CONTROL_ITEMS.has(type)) return "completed-only-control";
345
+ if (CODEX_COMPLETED_ONLY_TOOL_ITEMS.has(type)) return "completed-only-tool";
346
+ if (CODEX_TIMED_TOOL_ITEMS.has(type)) return "timed-tool";
347
+ return null;
348
+ }
349
+
350
+ function validCodexTodoList(item) {
351
+ return Array.isArray(item.items) && item.items.every((todo) => plainObject(todo)
352
+ && typeof todo.text === "string" && typeof todo.completed === "boolean");
353
+ }
354
+
355
+ function validCodexUsage(usage) {
356
+ return plainObject(usage) && CODEX_USAGE_KEYS.every((key) => Number.isSafeInteger(usage[key]) && usage[key] >= 0)
357
+ && (usage.cache_write_input_tokens === undefined
358
+ || Number.isSafeInteger(usage.cache_write_input_tokens) && usage.cache_write_input_tokens >= 0);
359
+ }
360
+
361
+ function validCodexMcpResult(result) {
362
+ return plainObject(result) && Array.isArray(result.content);
363
+ }
364
+
365
+ function validCodexCollabAgents(states) {
366
+ return plainObject(states) && Object.entries(states).every(([id, agent]) => safeId(id) && plainObject(agent)
367
+ && CODEX_COLLAB_AGENT_STATUSES.has(agent.status)
368
+ && (agent.message === null || typeof agent.message === "string"));
369
+ }
370
+
371
+ function validCodexWebSearchAction(action) {
372
+ if (!plainObject(action) || !["search", "open_page", "find_in_page", "other"].includes(action.type)) return false;
373
+ if (action.type === "search") return (action.query === undefined || typeof action.query === "string")
374
+ && (action.queries === undefined || Array.isArray(action.queries) && action.queries.every((query) => typeof query === "string"));
375
+ if (action.type === "open_page") return action.url === undefined || typeof action.url === "string";
376
+ if (action.type === "find_in_page") return (action.url === undefined || typeof action.url === "string")
377
+ && (action.pattern === undefined || typeof action.pattern === "string");
378
+ return true;
379
+ }
380
+
381
+ function validCodexItemForEvent(eventType, item) {
382
+ if (CODEX_MODEL_OUTPUT_ITEMS.has(item.type)) {
383
+ return eventType === "item.completed" && typeof item.text === "string"
384
+ && (item.type !== "reasoning" || item.text.trim().length > 0);
385
+ }
386
+ if (item.type === "todo_list") {
387
+ return ["item.started", "item.updated", "item.completed"].includes(eventType) && validCodexTodoList(item);
388
+ }
389
+ if (item.type === "error") {
390
+ return eventType === "item.completed" && typeof item.message === "string" && item.message.length > 0;
391
+ }
392
+ if (item.type === "file_change") {
393
+ return eventType === "item.completed" && ["completed", "failed"].includes(item.status)
394
+ && Array.isArray(item.changes) && item.changes.every((change) => plainObject(change)
395
+ && typeof change.path === "string" && ["add", "delete", "update"].includes(change.kind));
396
+ }
397
+ if (item.type === "command_execution") {
398
+ const statusValid = eventType === "item.started"
399
+ ? item.status === "in_progress"
400
+ : eventType === "item.completed" && CODEX_COMMAND_TERMINAL_STATUSES.has(item.status);
401
+ return statusValid && typeof item.command === "string"
402
+ && (eventType === "item.started"
403
+ ? (item.aggregated_output === undefined || typeof item.aggregated_output === "string")
404
+ && (item.exit_code === undefined || item.exit_code === null)
405
+ : typeof item.aggregated_output === "string" && (item.exit_code === null || Number.isSafeInteger(item.exit_code)));
406
+ }
407
+ if (item.type === "mcp_tool_call") {
408
+ const statusValid = eventType === "item.started"
409
+ ? item.status === "in_progress"
410
+ : eventType === "item.completed" && CODEX_TOOL_TERMINAL_STATUSES.has(item.status);
411
+ return statusValid && typeof item.server === "string" && item.server.length > 0
412
+ && typeof item.tool === "string" && item.tool.length > 0
413
+ && (eventType === "item.started" || Object.hasOwn(item, "arguments"))
414
+ && (item.result === undefined || item.result === null || validCodexMcpResult(item.result))
415
+ && (item.error === undefined || item.error === null
416
+ || plainObject(item.error) && typeof item.error.message === "string" && item.error.message.length > 0);
417
+ }
418
+ if (item.type === "collab_tool_call") {
419
+ const statusValid = eventType === "item.started"
420
+ ? item.status === "in_progress"
421
+ : eventType === "item.completed" && CODEX_TOOL_TERMINAL_STATUSES.has(item.status);
422
+ return statusValid && CODEX_COLLAB_TOOLS.has(item.tool) && Boolean(safeId(item.sender_thread_id))
423
+ && Array.isArray(item.receiver_thread_ids) && item.receiver_thread_ids.every((id) => safeId(id))
424
+ && (item.prompt === undefined || item.prompt === null || typeof item.prompt === "string")
425
+ && (eventType === "item.started"
426
+ ? item.agents_states === undefined || validCodexCollabAgents(item.agents_states)
427
+ : validCodexCollabAgents(item.agents_states));
428
+ }
429
+ return item.type === "web_search" && ["item.started", "item.completed"].includes(eventType) && Boolean(safeId(item.id))
430
+ && (eventType === "item.started"
431
+ ? (item.query === undefined || typeof item.query === "string")
432
+ && (item.action === undefined || validCodexWebSearchAction(item.action))
433
+ : typeof item.query === "string" && validCodexWebSearchAction(item.action));
434
+ }
435
+
436
+ function closeCodexModelTurnInterval(state, at) {
437
+ if (state.modelTurnStart === null) return;
438
+ if (addInterval(state, state.modelTurnIntervals, state.modelTurnStart, at)) state.matchedModelTurnIntervals += 1;
439
+ else state.modelTurnIntervalsInvalid = true;
440
+ state.modelTurnStart = null;
441
+ }
442
+
443
+ function consumeCodexEvent(state, event, at) {
444
+ if (state.terminalTurnSeen) {
445
+ invalidateEvent(state);
446
+ return;
447
+ }
448
+ if (event.type === "thread.started") {
449
+ if (state.jsonEvents !== 1 || !safeId(event.thread_id) || state.threadSeen || state.turnOpen) invalidateEvent(state);
450
+ else {
451
+ state.threadSeen = true;
452
+ state.threadStarts += 1;
453
+ }
454
+ return;
455
+ }
456
+ if (event.type === "turn.started") {
457
+ if (!state.threadSeen || state.terminalTurnSeen || state.turnOpen || state.modelTurnStart !== null) {
458
+ invalidateEvent(state);
459
+ return;
460
+ }
461
+ state.turnStarts += 1;
462
+ if (state.startupBoundary === null) state.startupBoundary = at;
463
+ state.turnOpen = true;
464
+ state.modelTurnStart = at;
465
+ return;
466
+ }
467
+ if (event.type === "turn.failed") {
468
+ if (!state.turnOpen || !plainObject(event.error) || typeof event.error.message !== "string" || !event.error.message) {
469
+ invalidateEvent(state);
470
+ return;
471
+ }
472
+ if (state.modelTurnStart !== null) {
473
+ state.unmatchedModelTurnStarts += 1;
474
+ state.modelTurnStart = null;
475
+ }
476
+ state.unmatchedToolStarts += state.openTools.size;
477
+ state.openTools.clear();
478
+ if (state.openTodoItemId !== null) state.openTodoItemId = null;
479
+ state.turnOpen = false;
480
+ state.terminalTurnSeen = true;
481
+ return;
482
+ }
483
+ if (event.type === "error") {
484
+ if (!state.threadSeen || typeof event.message !== "string" || !event.message) {
485
+ invalidateEvent(state);
486
+ return;
487
+ }
488
+ state.providerErrorSeen = true;
489
+ return;
490
+ }
491
+ if (event.type === "turn.completed") {
492
+ if (!state.turnOpen || !validCodexUsage(event.usage) || state.openTools.size > 0 || state.openTodoItemId !== null) {
493
+ invalidateEvent(state);
494
+ return;
495
+ }
496
+ state.turnCompletions += 1;
497
+ closeCodexModelTurnInterval(state, at);
498
+ state.turnOpen = false;
499
+ state.terminalTurnSeen = true;
500
+ return;
501
+ }
502
+ if (event.type === "item.updated") {
503
+ if (!state.turnOpen || !plainObject(event.item) || !safeId(event.item.id)
504
+ || event.item.type !== "todo_list" || !validCodexItemForEvent(event.type, event.item)
505
+ || state.openTodoItemId !== event.item.id || state.seenCodexItemIds.get(event.item.id) !== "todo_list") invalidateEvent(state);
506
+ return;
507
+ }
508
+ if (!["item.started", "item.completed"].includes(event.type)) {
509
+ invalidateEvent(state);
510
+ return;
511
+ }
512
+ if (!plainObject(event.item) || !safeId(event.item.id) || typeof event.item.type !== "string" || !state.turnOpen) {
513
+ invalidateEvent(state);
514
+ return;
515
+ }
516
+ const kind = codexItemKind(event.item.type);
517
+ if (!kind || !validCodexItemForEvent(event.type, event.item)) {
518
+ invalidateEvent(state);
519
+ return;
520
+ }
521
+ if (event.type === "item.started") {
522
+ if (state.seenCodexItemIds.has(event.item.id)) {
523
+ invalidateEvent(state);
524
+ return;
525
+ }
526
+ if (kind === "control") {
527
+ if (state.openTodoItemId !== null) invalidateEvent(state);
528
+ else {
529
+ state.seenCodexItemIds.set(event.item.id, event.item.type);
530
+ state.openTodoItemId = event.item.id;
531
+ }
532
+ return;
533
+ }
534
+ const id = safeId(event.item.id);
535
+ if (!id || state.openTools.has(id)) { invalidateEvent(state); return; }
536
+ state.seenCodexItemIds.set(id, event.item.type);
537
+ closeCodexModelTurnInterval(state, at);
538
+ state.toolStarts += 1;
539
+ state.openTools.set(id, { at, type: event.item.type });
540
+ return;
541
+ }
542
+ if (kind === "control") {
543
+ if (state.openTodoItemId !== event.item.id || state.seenCodexItemIds.get(event.item.id) !== event.item.type) invalidateEvent(state);
544
+ else state.openTodoItemId = null;
545
+ return;
546
+ }
547
+ if (kind === "completed-only-control" || kind === "model-output" || kind === "completed-only-tool") {
548
+ if (state.seenCodexItemIds.has(event.item.id)) {
549
+ invalidateEvent(state);
550
+ return;
551
+ }
552
+ state.seenCodexItemIds.set(event.item.id, event.item.type);
553
+ }
554
+ if (kind === "completed-only-control") return;
555
+ if (kind === "completed-only-tool") {
556
+ state.toolCompletions += 1;
557
+ state.unmatchedToolCompletions += 1;
558
+ state.modelTurnIntervalsInvalid = true;
559
+ state.toolIntervalsInvalid = true;
560
+ return;
561
+ }
562
+ if (kind === "model-output") return;
563
+ state.toolCompletions += 1;
564
+ const id = safeId(event.item.id);
565
+ if (!id) {
566
+ invalidateEvent(state);
567
+ state.unmatchedToolCompletions += 1;
568
+ return;
569
+ }
570
+ const seenType = state.seenCodexItemIds.get(id);
571
+ const started = state.openTools.get(id);
572
+ let matched = false;
573
+ if (started === undefined) {
574
+ if (seenType !== undefined) invalidateEvent(state);
575
+ else state.seenCodexItemIds.set(id, event.item.type);
576
+ state.unmatchedToolCompletions += 1;
577
+ state.modelTurnIntervalsInvalid = true;
578
+ state.toolIntervalsInvalid = true;
579
+ }
580
+ else if (seenType !== event.item.type || started.type !== event.item.type) {
581
+ invalidateEvent(state);
582
+ state.unmatchedToolCompletions += 1;
583
+ state.modelTurnIntervalsInvalid = true;
584
+ state.toolIntervalsInvalid = true;
585
+ }
586
+ else {
587
+ if (!addInterval(state, state.toolIntervals, started.at, at)) state.toolIntervalsInvalid = true;
588
+ state.matchedToolIntervals += 1;
589
+ state.openTools.delete(id);
590
+ matched = true;
591
+ }
592
+ if (matched && state.openTools.size === 0) {
593
+ if (state.modelTurnStart !== null) state.unmatchedModelTurnStarts += 1;
594
+ state.modelTurnStart = at;
595
+ }
596
+ }
597
+
598
+ function unavailableReason(state, kind) {
599
+ if (state.malformedLines > 0 || state.invalidObservationClock || state.providerErrorSeen) return "invalid-event-stream";
600
+ if (kind === "startup" && state.startupBoundary === null) return "missing-turn-start-boundary";
601
+ if (kind === "modelTurn" && (state.modelTurnIntervalsInvalid || state.unmatchedModelTurnStarts > 0 || state.turnStarts === 0)) return "incomplete-model-turn-boundaries";
602
+ if (kind === "tool" && (state.toolIntervalsInvalid || state.unmatchedToolStarts > 0 || state.unmatchedToolCompletions > 0 || state.openTools.size > 0)) return "incomplete-tool-boundaries";
603
+ return "unavailable";
604
+ }
605
+
606
+ export function unavailableBenchmarkTimingDiagnostics(surface, durationSeconds) {
607
+ if (!SURFACES.has(surface)) throw new Error(`Unsupported benchmark timing surface: ${surface}`);
608
+ const processDurationSeconds = finiteNonnegative(durationSeconds) ? rounded(durationSeconds, 6) : null;
609
+ return {
610
+ schemaVersion: 1,
611
+ authority: "observational-only",
612
+ gateImpact: "none",
613
+ clock: "benchmark-process-monotonic-receipt",
614
+ surface,
615
+ status: "unavailable",
616
+ processDurationSeconds,
617
+ phases: {
618
+ processStartup: phase("unavailable", null, "invalid-event-stream", "process-start-to-turn-start"),
619
+ modelTurnWait: phase("unavailable", null, "invalid-event-stream", "request-ready-to-next-model-output-boundary"),
620
+ toolExecution: phase("unavailable", null, "invalid-event-stream", "matched-tool-start-to-tool-completion"),
621
+ other: phase("unavailable", null, "one-or-more-phase-boundaries-unavailable", "process-duration-minus-available-observed-phases")
622
+ },
623
+ observations: {
624
+ jsonEvents: 0, sessionHeaders: 0, agentStarts: 0, agentEnds: 0, agentSettled: 0, threadStarts: 0,
625
+ turnStarts: 0, turnCompletions: 0, matchedModelTurnIntervals: 0,
626
+ toolStarts: 0, toolCompletions: 0, matchedToolIntervals: 0,
627
+ unmatchedToolStarts: 0, unmatchedToolCompletions: 0, unmatchedModelTurnStarts: 0,
628
+ malformedLines: 1, invalidClockBoundaries: 0, coalescedBoundaries: 0
629
+ },
630
+ privacy: { rawPayloadStored: false, promptsStored: false, commandsStored: false, pathsStored: false, identifiersStored: false }
631
+ };
632
+ }
633
+
634
+ function finish(state, durationSeconds) {
635
+ const durationAvailable = finiteNonnegative(durationSeconds);
636
+ if (durationAvailable && state.lastObservationAt !== null && state.lastObservationAt > durationSeconds + 1e-6) {
637
+ state.invalidObservationClock = true;
638
+ state.invalidClockBoundaries += 1;
639
+ }
640
+ const lifecycleComplete = state.surface === "codex-cli"
641
+ ? state.threadSeen && state.terminalTurnSeen && state.turnStarts > 0
642
+ && state.turnCompletions === state.turnStarts && !state.turnOpen
643
+ : state.turnStarts > 0 && state.turnCompletions === state.turnStarts && state.assistantBoundaries === state.turnStarts
644
+ && state.sessionSeen && state.agentStarted && state.agentEnded && state.agentSettled
645
+ && !state.agentCycleOpen && state.lastAgentEndWillRetry === false && state.agentStarts === state.agentEnds
646
+ && !state.turnOpen && state.modelTurnStart === null;
647
+ if (state.modelTurnStart !== null) state.unmatchedModelTurnStarts += 1;
648
+ state.unmatchedToolStarts += state.openTools.size;
649
+ const startupAvailable = durationAvailable && state.malformedLines === 0 && !state.invalidObservationClock
650
+ && !state.providerErrorSeen && state.startupBoundary !== null && state.startupBoundary <= durationSeconds;
651
+ const modelTurnAvailable = durationAvailable && state.malformedLines === 0 && !state.invalidObservationClock
652
+ && !state.providerErrorSeen && lifecycleComplete && state.unmatchedModelTurnStarts === 0 && !state.modelTurnIntervalsInvalid;
653
+ const toolAvailable = durationAvailable && state.malformedLines === 0 && !state.invalidObservationClock
654
+ && !state.providerErrorSeen && lifecycleComplete && state.unmatchedToolStarts === 0
655
+ && state.unmatchedToolCompletions === 0 && !state.toolIntervalsInvalid;
656
+ const startupSeconds = startupAvailable ? state.startupBoundary : null;
657
+ const modelTurnSeconds = modelTurnAvailable ? intervalDuration(state.modelTurnIntervals) : null;
658
+ const toolSeconds = toolAvailable ? intervalDuration(state.toolIntervals) : null;
659
+ const attributed = startupAvailable && modelTurnAvailable && toolAvailable
660
+ ? startupSeconds + modelTurnSeconds + toolSeconds
661
+ : null;
662
+ const otherAvailable = Number.isFinite(attributed) && attributed <= durationSeconds + 1e-6;
663
+ const otherSeconds = otherAvailable ? Math.max(0, durationSeconds - attributed) : null;
664
+ const availablePhases = [startupAvailable, modelTurnAvailable, toolAvailable, otherAvailable].filter(Boolean).length;
665
+ const status = availablePhases === PHASES.length ? "complete" : availablePhases > 0 ? "partial" : "unavailable";
666
+ return {
667
+ schemaVersion: 1,
668
+ authority: "observational-only",
669
+ gateImpact: "none",
670
+ clock: "benchmark-process-monotonic-receipt",
671
+ surface: state.surface,
672
+ status,
673
+ processDurationSeconds: durationAvailable ? rounded(durationSeconds, 6) : null,
674
+ phases: {
675
+ processStartup: startupAvailable
676
+ ? phase("available", startupSeconds, "observed-boundaries", "process-start-to-turn-start")
677
+ : phase("unavailable", null, unavailableReason(state, "startup"), "process-start-to-turn-start"),
678
+ modelTurnWait: modelTurnAvailable
679
+ ? phase("available", modelTurnSeconds, "observed-boundaries", "request-ready-to-next-model-output-boundary")
680
+ : phase("unavailable", null, unavailableReason(state, "modelTurn"), "request-ready-to-next-model-output-boundary"),
681
+ toolExecution: toolAvailable
682
+ ? phase("available", toolSeconds, "observed-boundaries", "matched-tool-start-to-tool-completion")
683
+ : phase("unavailable", null, unavailableReason(state, "tool"), "matched-tool-start-to-tool-completion"),
684
+ other: otherAvailable
685
+ ? phase("available", otherSeconds, "exact-unattributed-remainder", "process-duration-minus-available-observed-phases")
686
+ : phase("unavailable", null, "one-or-more-phase-boundaries-unavailable", "process-duration-minus-available-observed-phases")
687
+ },
688
+ observations: {
689
+ jsonEvents: state.jsonEvents,
690
+ sessionHeaders: state.sessionHeaders,
691
+ agentStarts: state.agentStarts,
692
+ agentEnds: state.agentEnds,
693
+ agentSettled: state.agentSettledEvents,
694
+ threadStarts: state.threadStarts,
695
+ turnStarts: state.turnStarts,
696
+ turnCompletions: state.turnCompletions,
697
+ matchedModelTurnIntervals: state.matchedModelTurnIntervals,
698
+ toolStarts: state.toolStarts,
699
+ toolCompletions: state.toolCompletions,
700
+ matchedToolIntervals: state.matchedToolIntervals,
701
+ unmatchedToolStarts: state.unmatchedToolStarts,
702
+ unmatchedToolCompletions: state.unmatchedToolCompletions,
703
+ unmatchedModelTurnStarts: state.unmatchedModelTurnStarts,
704
+ malformedLines: state.malformedLines,
705
+ invalidClockBoundaries: state.invalidClockBoundaries,
706
+ coalescedBoundaries: state.coalescedBoundaries
707
+ },
708
+ privacy: {
709
+ rawPayloadStored: false,
710
+ promptsStored: false,
711
+ commandsStored: false,
712
+ pathsStored: false,
713
+ identifiersStored: false
714
+ }
715
+ };
716
+ }
717
+
718
+ export function createBenchmarkTimingCollector({ surface }) {
719
+ if (!SURFACES.has(surface)) throw new Error(`Unsupported benchmark timing surface: ${surface}`);
720
+ const state = {
721
+ surface,
722
+ jsonEvents: 0, sessionHeaders: 0, agentStarts: 0, agentEnds: 0, agentSettledEvents: 0,
723
+ threadStarts: 0, turnStarts: 0, turnCompletions: 0, assistantBoundaries: 0,
724
+ toolStarts: 0, toolCompletions: 0, matchedToolIntervals: 0, matchedModelTurnIntervals: 0,
725
+ unmatchedToolStarts: 0, unmatchedToolCompletions: 0, unmatchedModelTurnStarts: 0,
726
+ malformedLines: 0, invalidClockBoundaries: 0, coalescedBoundaries: 0, invalidObservationClock: false,
727
+ startupBoundary: null, modelTurnStart: null,
728
+ modelTurnIntervals: [],
729
+ modelTurnIntervalsInvalid: false,
730
+ toolIntervals: [],
731
+ toolIntervalsInvalid: false,
732
+ openTools: new Map(),
733
+ completedTurnTools: new Map(),
734
+ turnToolResultIdentities: new Map(),
735
+ openToolResultIdentity: null,
736
+ seenCodexItemIds: new Map(),
737
+ turnOpen: false, assistantSeenInTurn: false, assistantMessageEnded: false, openMessageRole: null, openCustomType: null,
738
+ turnToolStarts: 0, turnToolCompletions: 0, turnToolResultMessages: 0, openTodoItemId: null,
739
+ agentStarted: false, agentCycleOpen: false, agentEnded: false, lastAgentEndWillRetry: null,
740
+ agentSettled: false, sessionSeen: false, threadSeen: false, terminalTurnSeen: false, providerErrorSeen: false,
741
+ lastObservationAt: null
742
+ };
743
+ const decoder = new StringDecoder("utf8");
744
+ let buffer = "";
745
+ let bufferedAt = 0;
746
+ let finished = false;
747
+ const consumeLine = (raw, at) => {
748
+ const line = raw.trim();
749
+ if (!line) return;
750
+ let event;
751
+ try { event = JSON.parse(line); }
752
+ catch { state.malformedLines += 1; return; }
753
+ if (!plainObject(event) || typeof event.type !== "string") { state.malformedLines += 1; return; }
754
+ state.jsonEvents += 1;
755
+ if (surface === "codex-cli") consumeCodexEvent(state, event, at);
756
+ else consumePiEvent(state, event, at);
757
+ };
758
+ return {
759
+ write(chunk, observedAtSeconds) {
760
+ if (finished) throw new Error("Benchmark timing collector is already finished");
761
+ if (!finiteNonnegative(observedAtSeconds) || observedAtSeconds < bufferedAt) state.invalidObservationClock = true;
762
+ bufferedAt = finiteNonnegative(observedAtSeconds) ? observedAtSeconds : bufferedAt;
763
+ if (finiteNonnegative(observedAtSeconds)) state.lastObservationAt = observedAtSeconds;
764
+ buffer += typeof chunk === "string" ? chunk : decoder.write(Buffer.from(chunk));
765
+ let newline;
766
+ while ((newline = buffer.indexOf("\n")) >= 0) {
767
+ consumeLine(buffer.slice(0, newline), bufferedAt);
768
+ buffer = buffer.slice(newline + 1);
769
+ }
770
+ if (Buffer.byteLength(buffer) > MAX_JSONL_BYTES) { state.malformedLines += 1; buffer = ""; }
771
+ },
772
+ finish(durationSeconds) {
773
+ if (finished) throw new Error("Benchmark timing collector is already finished");
774
+ finished = true;
775
+ buffer += decoder.end();
776
+ if (buffer) consumeLine(buffer, bufferedAt);
777
+ buffer = "";
778
+ return finish(state, durationSeconds);
779
+ }
780
+ };
781
+ }
782
+
783
+ export function createDeferredBenchmarkTimingCollector({
784
+ surface,
785
+ maximumBytes = MAX_TIMING_REPLAY_BYTES,
786
+ collectorFactory = createBenchmarkTimingCollector
787
+ }) {
788
+ if (!SURFACES.has(surface)) throw new Error(`Unsupported benchmark timing surface: ${surface}`);
789
+ if (!Number.isSafeInteger(maximumBytes) || maximumBytes <= 0) throw new Error("Benchmark timing replay limit must be a positive safe integer");
790
+ if (typeof collectorFactory !== "function") throw new Error("Benchmark timing collector factory must be a function");
791
+ const entries = [];
792
+ let capturedBytes = 0;
793
+ let overflow = false;
794
+ let finished = false;
795
+ const clear = () => {
796
+ for (const entry of entries) { try { entry.chunk.fill(0); } catch { /* Best-effort zeroing; release references below. */ } }
797
+ entries.length = 0;
798
+ capturedBytes = 0;
799
+ };
800
+ return {
801
+ write(chunk, observedAtSeconds) {
802
+ if (finished) throw new Error("Deferred benchmark timing collector is already finished");
803
+ if (overflow) return;
804
+ try {
805
+ const byteLength = Buffer.byteLength(chunk);
806
+ if (byteLength > maximumBytes - capturedBytes) {
807
+ overflow = true;
808
+ clear();
809
+ return;
810
+ }
811
+ const copy = Buffer.from(chunk);
812
+ entries.push({ chunk: copy, observedAtSeconds });
813
+ capturedBytes += copy.length;
814
+ } catch {
815
+ overflow = true;
816
+ clear();
817
+ }
818
+ },
819
+ discard() {
820
+ finished = true;
821
+ clear();
822
+ },
823
+ finish(durationSeconds) {
824
+ if (finished) throw new Error("Deferred benchmark timing collector is already finished");
825
+ finished = true;
826
+ try {
827
+ const collector = collectorFactory({ surface });
828
+ if (overflow) collector.write("invalid-bounded-timing-replay\n", 0);
829
+ else for (const entry of entries) collector.write(entry.chunk, entry.observedAtSeconds);
830
+ return collector.finish(durationSeconds);
831
+ } catch {
832
+ return unavailableBenchmarkTimingDiagnostics(surface, durationSeconds);
833
+ } finally {
834
+ clear();
835
+ }
836
+ }
837
+ };
838
+ }
839
+
840
+ function validPhase(value, name) {
841
+ const expectedBoundary = {
842
+ processStartup: "process-start-to-turn-start",
843
+ modelTurnWait: "request-ready-to-next-model-output-boundary",
844
+ toolExecution: "matched-tool-start-to-tool-completion",
845
+ other: "process-duration-minus-available-observed-phases"
846
+ }[name];
847
+ const unavailableReasons = {
848
+ processStartup: new Set(["invalid-event-stream", "missing-turn-start-boundary", "unavailable"]),
849
+ modelTurnWait: new Set(["invalid-event-stream", "incomplete-model-turn-boundaries", "unavailable"]),
850
+ toolExecution: new Set(["invalid-event-stream", "incomplete-tool-boundaries", "unavailable"]),
851
+ other: new Set(["one-or-more-phase-boundaries-unavailable"])
852
+ }[name];
853
+ const expectedAvailableReason = name === "other" ? "exact-unattributed-remainder" : "observed-boundaries";
854
+ return plainObject(value)
855
+ && Object.keys(value).length === 4
856
+ && ["available", "unavailable"].includes(value.status)
857
+ && (value.status === "available" ? finiteNonnegative(value.seconds) : value.seconds === null)
858
+ && value.boundary === expectedBoundary
859
+ && (value.status === "available" ? value.reason === expectedAvailableReason : unavailableReasons.has(value.reason));
860
+ }
861
+
862
+ export function validBenchmarkTimingDiagnostics(value, surface, durationSeconds) {
863
+ const topLevelKeys = ["schemaVersion", "authority", "gateImpact", "clock", "surface", "status", "processDurationSeconds", "phases", "observations", "privacy"];
864
+ if (!plainObject(value) || Object.keys(value).length !== topLevelKeys.length || !topLevelKeys.every((key) => Object.hasOwn(value, key))
865
+ || value.schemaVersion !== 1 || value.authority !== "observational-only"
866
+ || value.gateImpact !== "none" || value.clock !== "benchmark-process-monotonic-receipt"
867
+ || value.surface !== surface || !["complete", "partial", "unavailable"].includes(value.status)
868
+ || !finiteNonnegative(value.processDurationSeconds)
869
+ || !plainObject(value.phases) || Object.keys(value.phases).length !== PHASES.length
870
+ || !PHASES.every((name) => validPhase(value.phases[name], name))) return false;
871
+ if (finiteNonnegative(durationSeconds) && Math.abs(value.processDurationSeconds - durationSeconds) > 1e-5) return false;
872
+ const observationKeys = ["jsonEvents", "sessionHeaders", "agentStarts", "agentEnds", "agentSettled", "threadStarts",
873
+ "turnStarts", "turnCompletions", "matchedModelTurnIntervals", "toolStarts", "toolCompletions", "matchedToolIntervals",
874
+ "unmatchedToolStarts", "unmatchedToolCompletions", "unmatchedModelTurnStarts", "malformedLines", "invalidClockBoundaries", "coalescedBoundaries"];
875
+ if (!plainObject(value.observations) || Object.keys(value.observations).length !== observationKeys.length
876
+ || !observationKeys.every((key) => Number.isSafeInteger(value.observations[key]) && value.observations[key] >= 0)) return false;
877
+ const observations = value.observations;
878
+ const lifecycleEvents = surface === "codex-cli"
879
+ ? observations.threadStarts
880
+ : observations.sessionHeaders + observations.agentStarts + observations.agentEnds + observations.agentSettled;
881
+ if ([observations.sessionHeaders, observations.agentSettled, observations.threadStarts]
882
+ .some((count) => count > 1)
883
+ || (surface === "codex-cli"
884
+ ? observations.sessionHeaders + observations.agentStarts + observations.agentEnds + observations.agentSettled !== 0
885
+ : observations.threadStarts !== 0 || observations.agentEnds > observations.agentStarts)
886
+ || observations.turnCompletions > observations.turnStarts
887
+ || (surface !== "codex-cli" && observations.matchedModelTurnIntervals > observations.turnStarts)
888
+ || observations.matchedToolIntervals > observations.toolStarts
889
+ || observations.matchedToolIntervals > observations.toolCompletions
890
+ || observations.matchedModelTurnIntervals > observations.jsonEvents
891
+ || (surface === "codex-cli" && (observations.turnStarts > 1 || observations.turnCompletions > 1
892
+ || observations.matchedModelTurnIntervals > observations.toolStarts + observations.turnCompletions))
893
+ || lifecycleEvents + observations.turnStarts + observations.turnCompletions
894
+ + observations.toolStarts + observations.toolCompletions > observations.jsonEvents) return false;
895
+ const surfaceHeaderObserved = surface === "codex-cli" ? observations.threadStarts === 1 : observations.sessionHeaders === 1;
896
+ const lifecycleComplete = surface === "codex-cli"
897
+ ? surfaceHeaderObserved && observations.turnStarts === 1 && observations.turnCompletions === 1
898
+ : surfaceHeaderObserved && observations.agentStarts > 0 && observations.agentEnds === observations.agentStarts
899
+ && observations.agentSettled === 1 && observations.turnStarts > 0 && observations.turnCompletions === observations.turnStarts;
900
+ const privacyKeys = ["rawPayloadStored", "promptsStored", "commandsStored", "pathsStored", "identifiersStored"];
901
+ if (!plainObject(value.privacy) || Object.keys(value.privacy).length !== privacyKeys.length
902
+ || !privacyKeys.every((key) => value.privacy[key] === false)) return false;
903
+ const available = PHASES.filter((name) => value.phases[name].status === "available").length;
904
+ const expectedStatus = available === PHASES.length ? "complete" : available > 0 ? "partial" : "unavailable";
905
+ if (value.status !== expectedStatus) return false;
906
+ if (PHASES.some((name) => value.phases[name].status === "available" && value.phases[name].seconds > value.processDurationSeconds + 1e-6)) return false;
907
+ if ((value.observations.malformedLines > 0 || value.observations.invalidClockBoundaries > 0) && available > 0) return false;
908
+ if (value.status === "complete" && ["unmatchedToolStarts", "unmatchedToolCompletions", "unmatchedModelTurnStarts",
909
+ "malformedLines", "invalidClockBoundaries", "coalescedBoundaries"].some((key) => value.observations[key] > 0)) return false;
910
+ if (value.status === "complete" && (!lifecycleComplete
911
+ || observations.matchedModelTurnIntervals === 0
912
+ || (surface !== "codex-cli" && observations.matchedModelTurnIntervals !== observations.turnStarts)
913
+ || observations.toolStarts !== observations.toolCompletions
914
+ || observations.matchedToolIntervals !== observations.toolStarts)) return false;
915
+ if (value.phases.processStartup.status === "available" && (!surfaceHeaderObserved || observations.turnStarts === 0
916
+ || surface !== "codex-cli" && observations.agentStarts === 0)) return false;
917
+ if (value.phases.modelTurnWait.status === "available" && (!lifecycleComplete
918
+ || observations.matchedModelTurnIntervals === 0
919
+ || (surface !== "codex-cli" && observations.matchedModelTurnIntervals !== observations.turnStarts)
920
+ || observations.unmatchedModelTurnStarts > 0)) return false;
921
+ if (value.phases.toolExecution.status === "available" && (!lifecycleComplete
922
+ || observations.toolStarts !== observations.toolCompletions
923
+ || observations.matchedToolIntervals !== observations.toolStarts
924
+ || observations.unmatchedToolStarts > 0 || observations.unmatchedToolCompletions > 0)) return false;
925
+ const attributedPhasesAvailable = ["processStartup", "modelTurnWait", "toolExecution"]
926
+ .every((name) => value.phases[name].status === "available");
927
+ if ((value.phases.other.status === "available") !== attributedPhasesAvailable) return false;
928
+ if (value.phases.other.status === "available") {
929
+ const sum = PHASES.reduce((total, name) => total + value.phases[name].seconds, 0);
930
+ if (Math.abs(sum - value.processDurationSeconds) > 5e-5) return false;
931
+ }
932
+ return true;
933
+ }
934
+
935
+ export function canonicalBenchmarkTimingDiagnostics(value, surface, durationSeconds) {
936
+ try {
937
+ return validBenchmarkTimingDiagnostics(value, surface, durationSeconds) ? value : undefined;
938
+ } catch {
939
+ return undefined;
940
+ }
941
+ }
942
+
943
+ export function summarizeBenchmarkTimingDiagnostics(runs) {
944
+ const values = Array.isArray(runs) ? runs : [];
945
+ const surfaces = {};
946
+ for (const surface of [...new Set(values.map((run) => run?.surface).filter((value) => typeof value === "string"))].sort()) {
947
+ const surfaceRuns = values.filter((run) => run?.surface === surface);
948
+ const valid = surfaceRuns.filter((run) => canonicalBenchmarkTimingDiagnostics(run.timingDiagnostics, surface, run.durationSeconds));
949
+ const phaseSummary = Object.fromEntries(PHASES.map((name) => {
950
+ const available = valid.map((run) => run.timingDiagnostics.phases[name]).filter((item) => item.status === "available");
951
+ return [name, {
952
+ availableRuns: available.length,
953
+ unavailableRuns: surfaceRuns.length - available.length,
954
+ medianSeconds: available.length > 0 ? rounded(median(available.map((item) => item.seconds)), 6) : null
955
+ }];
956
+ }));
957
+ surfaces[surface] = {
958
+ runs: surfaceRuns.length,
959
+ validDiagnostics: valid.length,
960
+ completeDiagnostics: valid.filter((run) => run.timingDiagnostics.status === "complete").length,
961
+ phases: phaseSummary
962
+ };
963
+ }
964
+ return {
965
+ schemaVersion: 1,
966
+ authority: "observational-only",
967
+ gateImpact: "none",
968
+ clock: "benchmark-process-monotonic-receipt",
969
+ surfaces
970
+ };
971
+ }