Skip to content

feat(agent-runtime): expose per-message/tool/run timing via eggExt namespace#457

Merged
jerryliang64 merged 1 commit into
masterfrom
feat-agent-message-timing
Jun 18, 2026
Merged

feat(agent-runtime): expose per-message/tool/run timing via eggExt namespace#457
jerryliang64 merged 1 commit into
masterfrom
feat-agent-message-timing

Conversation

@jerryliang64

@jerryliang64 jerryliang64 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Why

Session data lacks timing: per-message latency, per-tool start/end/duration, and ms-granular run timing. The Claude Agent SDK only reports a whole-conversation total on the result message — individual assistant/user/tool_use messages carry no time at all. This PR adds the storage/contract-layer fields to carry that timing (the actual stamping happens executor-side).

What

All framework extension fields live under a single top-level eggExt namespace so they never collide with Claude Agent SDK message fields (the SDK message payload is untouched). Time-field naming: absolute epoch-ms points end in AtMs, durations/intervals in Ms; existing seconds-level run fields are kept for back-compat.

  • types: MessageTiming / ToolTiming / EggExt; every AgentMessage member carries an optional eggExt. RunRecord/RunObject gain ms timing (startedAtMs/completedAtMs/durationMs/apiDurationMs).
  • RunBuilder: records ms timing and derives durationMs (null when restored without startedAtMs); seconds fields are derived from the same ms reading to avoid cross-second drift.
  • MessageConverter.extractApiDurationMs: sums result.duration_api_ms.
  • OSSAgentStore.appendMessages: stamps a server-side eggExt.persistedAtMs on each persisted message (shallow-copied; never overwrites executor-stamped timing).

Compatibility

All new fields are optional; seconds-level run fields unchanged; old consumers ignore eggExt. getThread still returns only user/assistant by default, and the eggExt (timing/toolTimings) on those is visible alongside them.

Test

New coverage for ms timing, extractApiDurationMs, and persistedAtMs stamping; full agent-runtime suite green (200 passing).

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added millisecond-precision run lifecycle metrics (started/completed, total duration) plus apiDurationMs, now captured and exposed in run snapshots.
    • Persisted messages are now stamped with persistedAtMs at append time (without mutating the original inputs). Message shapes now support optional timing metadata.
  • Tests

    • Added unit coverage for apiDurationMs extraction, message stamping (including no input mutation), and RunBuilder millisecond timing restoration/snapshotting.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0f8ee723-9fd0-4bb3-b7ed-f73685c94bad

📥 Commits

Reviewing files that changed from the base of the PR and between 6ed3a1e and 3a5fe13.

📒 Files selected for processing (11)
  • core/agent-runtime/src/AgentRuntime.ts
  • core/agent-runtime/src/AgentStoreUtils.ts
  • core/agent-runtime/src/MessageConverter.ts
  • core/agent-runtime/src/OSSAgentStore.ts
  • core/agent-runtime/src/RunBuilder.ts
  • core/agent-runtime/test/MessageConverter.test.ts
  • core/agent-runtime/test/OSSAgentStore.test.ts
  • core/agent-runtime/test/RunBuilder.test.ts
  • core/types/agent-runtime/AgentMessage.ts
  • core/types/agent-runtime/AgentRuntime.ts
  • core/types/agent-runtime/AgentStore.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • core/types/agent-runtime/AgentRuntime.ts
  • core/agent-runtime/test/MessageConverter.test.ts
  • core/agent-runtime/test/RunBuilder.test.ts
  • core/agent-runtime/test/OSSAgentStore.test.ts
  • core/agent-runtime/src/MessageConverter.ts
  • core/agent-runtime/src/AgentStoreUtils.ts
  • core/types/agent-runtime/AgentStore.ts
  • core/agent-runtime/src/OSSAgentStore.ts
  • core/agent-runtime/src/AgentRuntime.ts
  • core/agent-runtime/src/RunBuilder.ts
  • core/types/agent-runtime/AgentMessage.ts

📝 Walkthrough

Walkthrough

Adds millisecond-granular timing fields (startedAtMs, completedAtMs, durationMs, apiDurationMs) to RunRecord, RunObject, and RunBuilder; introduces nowMs() and MessageConverter.extractApiDurationMs(); wires API duration through AgentRuntime completion; stamps persistedAtMs into messages on OSSAgentStore.appendMessages; and extends AgentMessage with an eggExt metadata envelope.

Changes

Millisecond-granular timing and persistedAtMs message stamping

Layer / File(s) Summary
Type contracts: EggExt, RunRecord, RunObject ms fields
core/types/agent-runtime/AgentMessage.ts, core/types/agent-runtime/AgentStore.ts, core/types/agent-runtime/AgentRuntime.ts
Introduces MessageTiming, ToolTiming, EggExt, and AgentMessageExtensions in AgentMessage; all SDK message interfaces extend AgentMessageExtensions; RunRecord and RunObject gain optional startedAtMs, completedAtMs, durationMs, and apiDurationMs fields.
nowMs helper and extractApiDurationMs utility
core/agent-runtime/src/AgentStoreUtils.ts, core/agent-runtime/src/MessageConverter.ts, core/agent-runtime/test/MessageConverter.test.ts
nowMs() returns Date.now() for ms-granular timestamps; MessageConverter.extractApiDurationMs() sums duration_api_ms from result-type messages; tests cover missing data, single, multi-result, and non-numeric cases.
RunBuilder ms-granular start/complete/snapshot
core/agent-runtime/src/RunBuilder.ts, core/agent-runtime/test/RunBuilder.test.ts
Adds startedAtMs/completedAtMs/durationMs/apiDurationMs private fields; start() records startedAtMs via nowMs(); complete() accepts apiDurationMs, computes completedAtMs and durationMs; snapshot() exposes all four ms fields; fromRecord() restores ms fields; tests verify all transitions and null defaults.
OSSAgentStore persistedAtMs stamping on append
core/agent-runtime/src/OSSAgentStore.ts, core/agent-runtime/test/OSSAgentStore.test.ts
appendMessages shallow-copies each message, merges existing eggExt with { persistedAtMs: nowMs() }, serializes stamped copies to JSONL without mutating caller objects; tests verify stamp presence, existing eggExt field preservation, and immutability of input messages.
AgentRuntime wiring of apiDurationMs into rb.complete
core/agent-runtime/src/AgentRuntime.ts
syncRun, asyncRun, and streamRun success paths pass MessageConverter.extractApiDurationMs(streamMessages) as a second argument to rb.complete(); adjusts one comment near terminal-status handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • eggjs/tegg#433: Both PRs extend the run-completion flow in AgentRuntime.ts and RunBuilder.ts, with this PR passing apiDurationMs to the rb.complete() signature.

Suggested reviewers

  • akitaSummer

🐇 A hop, a tick, a millisecond bright,
The run now knows how long it took to ignite.
With nowMs() stamped on every stored line,
apiDurationMs flows through the pipeline so fine.
EggExt holds timing, a neat little box —
The rabbit counts milliseconds, not ticks of a clock! 🕐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(agent-runtime): expose per-message/tool/run timing via eggExt namespace' accurately and clearly summarizes the main objective—adding timing information through an eggExt extension to agent-runtime.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-agent-message-timing

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

core/agent-runtime/src/AgentRuntime.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

core/agent-runtime/src/AgentStoreUtils.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

core/agent-runtime/src/MessageConverter.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

  • 8 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces millisecond-granular timing fields (startedAtMs, completedAtMs, durationMs, and apiDurationMs) across runs and messages, along with a server-side persistedAtMs timestamp for stored messages. The changes include helper functions, message extraction logic, updated types, and comprehensive unit tests. The review feedback highlights three key improvements: defensively guarding against null or undefined messages in MessageConverter, preventing negative run durations due to potential system clock drift in RunBuilder, and ensuring arrays are not incorrectly processed as objects in OSSAgentStore.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread core/agent-runtime/src/MessageConverter.ts
Comment thread core/agent-runtime/src/RunBuilder.ts
Comment thread core/agent-runtime/src/OSSAgentStore.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/types/agent-runtime/AgentMessage.ts (1)

128-139: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add duration_api_ms to SDKResultMessage to close the cross-file contract gap.

MessageConverter.extractApiDurationMs consumes result.duration_api_ms, but SDKResultMessage doesn’t declare it. That forces unsafe casts and makes this new timing path type-unsafe across files.

Suggested patch
 export interface SDKResultMessage extends AgentMessageExtensions {
   type: 'result';
   subtype: string;
+  /** Pure model API duration in ms, when provided by the SDK result payload. */
+  duration_api_ms?: number;
   usage?: {
     input_tokens?: number;
     output_tokens?: number;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/types/agent-runtime/AgentMessage.ts` around lines 128 - 139, The
SDKResultMessage interface is missing the duration_api_ms property that is being
consumed by MessageConverter.extractApiDurationMs, creating a type-safety gap.
Add an optional duration_api_ms field with a number type to the SDKResultMessage
interface definition to properly declare this contract and eliminate unsafe
casting across files.
🧹 Nitpick comments (1)
core/agent-runtime/test/MessageConverter.test.ts (1)

129-133: ⚡ Quick win

Extend this suite with NaN/Infinity/negative duration cases.

Given timing is now a surfaced contract, add edge-case assertions so invalid numeric values are ignored consistently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/agent-runtime/test/MessageConverter.test.ts` around lines 129 - 133, Add
additional test cases to the MessageConverter test suite to cover edge cases for
invalid duration values. After the existing test for non-numeric duration_api_ms
in the 'should ignore non-numeric duration_api_ms' test block, add separate test
cases that verify MessageConverter.extractApiDurationMs returns undefined for
NaN, Infinity, and negative numeric values passed as duration_api_ms. Each test
should follow the same assertion pattern as the existing test, passing the
respective invalid value and asserting the result is undefined, ensuring all
invalid numeric and non-numeric duration values are handled consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/agent-runtime/src/MessageConverter.ts`:
- Around line 47-50: The type check for duration_api_ms (the variable d) is
insufficient because typeof d === 'number' accepts NaN, Infinity, and negative
values. Replace the condition that checks `typeof d === 'number'` in the
duration_api_ms validation block with a stricter check using Number.isFinite(d)
&& d >= 0 to ensure only valid, finite, non-negative numbers are accepted and
accumulated into the total.

In `@core/agent-runtime/test/OSSAgentStore.test.ts`:
- Line 115: The linter requires spaces inside array brackets. In the
appendMessages call, change the array argument from [original] to [ original ]
by adding a space after the opening bracket and before the closing bracket to
satisfy the array-bracket spacing requirement.

---

Outside diff comments:
In `@core/types/agent-runtime/AgentMessage.ts`:
- Around line 128-139: The SDKResultMessage interface is missing the
duration_api_ms property that is being consumed by
MessageConverter.extractApiDurationMs, creating a type-safety gap. Add an
optional duration_api_ms field with a number type to the SDKResultMessage
interface definition to properly declare this contract and eliminate unsafe
casting across files.

---

Nitpick comments:
In `@core/agent-runtime/test/MessageConverter.test.ts`:
- Around line 129-133: Add additional test cases to the MessageConverter test
suite to cover edge cases for invalid duration values. After the existing test
for non-numeric duration_api_ms in the 'should ignore non-numeric
duration_api_ms' test block, add separate test cases that verify
MessageConverter.extractApiDurationMs returns undefined for NaN, Infinity, and
negative numeric values passed as duration_api_ms. Each test should follow the
same assertion pattern as the existing test, passing the respective invalid
value and asserting the result is undefined, ensuring all invalid numeric and
non-numeric duration values are handled consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4da5cd6e-2e42-4e60-8fa7-7317c8c09d41

📥 Commits

Reviewing files that changed from the base of the PR and between bf89468 and e98073b.

📒 Files selected for processing (11)
  • core/agent-runtime/src/AgentRuntime.ts
  • core/agent-runtime/src/AgentStoreUtils.ts
  • core/agent-runtime/src/MessageConverter.ts
  • core/agent-runtime/src/OSSAgentStore.ts
  • core/agent-runtime/src/RunBuilder.ts
  • core/agent-runtime/test/MessageConverter.test.ts
  • core/agent-runtime/test/OSSAgentStore.test.ts
  • core/agent-runtime/test/RunBuilder.test.ts
  • core/types/agent-runtime/AgentMessage.ts
  • core/types/agent-runtime/AgentRuntime.ts
  • core/types/agent-runtime/AgentStore.ts

Comment thread core/agent-runtime/src/MessageConverter.ts
Comment thread core/agent-runtime/test/OSSAgentStore.test.ts Outdated
@jerryliang64 jerryliang64 force-pushed the feat-agent-message-timing branch from e98073b to 8eae36b Compare June 18, 2026 03:43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
core/agent-runtime/test/OSSAgentStore.test.ts (1)

115-115: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix array-bracket spacing to unblock CI.

The linter requires spaces inside array brackets per the project's eslint configuration.

🔧 Proposed fix
-      await store.appendMessages(thread.id, [original]);
+      await store.appendMessages(thread.id, [ original ]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/agent-runtime/test/OSSAgentStore.test.ts` at line 115, The array bracket
spacing in the store.appendMessages call does not conform to the project's
eslint configuration which requires spaces inside array brackets. Modify the
array parameter in the appendMessages method call to add spaces inside the
brackets around the original element, changing [original] to [ original ].

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@core/agent-runtime/test/OSSAgentStore.test.ts`:
- Line 115: The array bracket spacing in the store.appendMessages call does not
conform to the project's eslint configuration which requires spaces inside array
brackets. Modify the array parameter in the appendMessages method call to add
spaces inside the brackets around the original element, changing [original] to [
original ].

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d8a6d4ad-221b-4f7c-9d84-218e2be853d2

📥 Commits

Reviewing files that changed from the base of the PR and between e98073b and 8eae36b.

📒 Files selected for processing (11)
  • core/agent-runtime/src/AgentRuntime.ts
  • core/agent-runtime/src/AgentStoreUtils.ts
  • core/agent-runtime/src/MessageConverter.ts
  • core/agent-runtime/src/OSSAgentStore.ts
  • core/agent-runtime/src/RunBuilder.ts
  • core/agent-runtime/test/MessageConverter.test.ts
  • core/agent-runtime/test/OSSAgentStore.test.ts
  • core/agent-runtime/test/RunBuilder.test.ts
  • core/types/agent-runtime/AgentMessage.ts
  • core/types/agent-runtime/AgentRuntime.ts
  • core/types/agent-runtime/AgentStore.ts
✅ Files skipped from review due to trivial changes (1)
  • core/types/agent-runtime/AgentRuntime.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • core/agent-runtime/src/AgentStoreUtils.ts
  • core/agent-runtime/test/MessageConverter.test.ts
  • core/agent-runtime/test/RunBuilder.test.ts
  • core/agent-runtime/src/OSSAgentStore.ts
  • core/agent-runtime/src/MessageConverter.ts
  • core/types/agent-runtime/AgentMessage.ts

@jerryliang64 jerryliang64 force-pushed the feat-agent-message-timing branch from 8eae36b to 6ed3a1e Compare June 18, 2026 03:58
…pace

Expose conversation timing the Claude Agent SDK does not provide, kept in a
single additive `eggExt` namespace so it never collides with SDK message fields
(the SDK `message` payload is untouched).

- types: add MessageTiming / ToolTiming / EggExt; every AgentMessage member
  carries an optional `eggExt`. RunRecord/RunObject gain ms-granular timing
  (startedAtMs/completedAtMs/durationMs/apiDurationMs); legacy seconds fields
  retained for back-compat.
- RunBuilder: record ms timing, derive durationMs = completedAtMs - startedAtMs
  (null when restored without startedAtMs); seconds fields derived from the same
  ms reading to avoid cross-second drift.
- MessageConverter.extractApiDurationMs: sum result.duration_api_ms.
- AgentRuntime: pass apiDurationMs into rb.complete at all three completion sites.
- OSSAgentStore.appendMessages: stamp a server-side eggExt.persistedAtMs on each
  persisted message (shallow-copied; never overwrites executor-stamped timing).
- tests: cover ms timing, extractApiDurationMs, persistedAtMs stamping.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jerryliang64 jerryliang64 merged commit 9f94e2e into master Jun 18, 2026
12 checks passed
@jerryliang64 jerryliang64 deleted the feat-agent-message-timing branch June 18, 2026 09:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants