Skip to content

feat(pi): add real-time context window reporting with configurable polling interval#1883

Open
theslava wants to merge 5 commits into
getpaseo:mainfrom
theslava:feat/pi-context-window-reporting
Open

feat(pi): add real-time context window reporting with configurable polling interval#1883
theslava wants to merge 5 commits into
getpaseo:mainfrom
theslava:feat/pi-context-window-reporting

Conversation

@theslava

@theslava theslava commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Linked issue

Closes # (behavioral improvement — no prior issue)

Type of change

  • Bug fix
  • New feature (with prior issue + design alignment)
  • Refactor / code improvement
  • Docs

What does this PR do

Adds real-time context window usage reporting for Pi agents during active turns, configurable via provider params.

New provider params under agents.providers.{pi|omp}.params:

  • contextWindowReportingEnabled (boolean, default false) — opt-in feature gate
  • pollingIntervalMs (number, default 5000ms) — customizes poll frequency

Changes:

  • Uses milestone-based emission at tool_execution_end and compaction_end events for zero-overhead granularity between polls
  • Deduplicates rapid poll results via buildUsageKey()
  • Configurable polling interval replaces hardcoded 5s

Files changed:

  • packages/server/src/server/agent/providers/pi/agent.ts (+115 lines)
  • packages/server/src/server/agent/providers/pi/agent.test.ts (+187 lines)
  • packages/server/src/server/agent/providers/pi/omp-integration.test.ts (3 new E2E tests against live OMP binary)

How did you verify it

  • Built with npm run build:server — passes
  • Unit tests pass on FakePi
  • 3 integration tests against live OMP binary verify the full stack end-to-end
  • Typecheck and lint pass

Checklist

  • One focused change. Unrelated cleanups split out.
  • npm run typecheck passes
  • npm run lint passes
  • npm run format ran (Biome)
  • UI changes include screenshots or video for every affected platform
  • Tests added or updated where it made sense

theslava added 2 commits July 3, 2026 04:34
…lling interval

Add periodic usage polling during active turns via get_session_stats RPC.
New provider params under agents.providers.{pi|omp}.params:
- contextWindowReportingEnabled (boolean, default false) — opt-in feature
- pollingIntervalMs (number, default 5000ms) — customizes poll frequency

Uses milestone-based emission at tool_execution_end and compaction_end
events for zero-overhead granularity between polls. Deduplicates rapid
poll results via buildUsageKey().

Adds 3 integration tests against live OMP binary to verify the full
stack end-to-end.
@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds opt-in real-time context window usage reporting to Pi (and OMP) agent sessions, driven by two new provider params: contextWindowReportingEnabled and pollingIntervalMs.

  • PiRpcAgentSession gains a setInterval-based poll (pollUsage) that fires usage_updated events during active turns, plus milestone emissions at tool_execution_end and compaction_end via maybeEmitUsageForTurn; deduplication is handled by a string key built from the last-seen usage snapshot.
  • provider-snapshot-manager.ts receives a one-line defensive fix that optional-chains fetchCatalog to avoid a crash when an injected test client omits that method.
  • 187 lines of unit tests and 3 OMP E2E integration tests cover the new code paths.

Confidence Score: 5/5

Safe to merge; the new polling code is entirely opt-in behind a false-by-default flag, existing behaviour is unaffected when the flag is off, and the provider-snapshot-manager fix is a straightforward defensive patch.

The polling feature is isolated to PiRpcAgentSession, gated by contextWindowReportingEnabled, and cleaned up on every turn-end and close path. The only new code quality issue found is duplication between pollUsage and maybeEmitUsageForTurn, which does not affect correctness.

No files require special attention for correctness; agent.ts has internal method duplication worth a follow-up cleanup.

Important Files Changed

Filename Overview
packages/server/src/server/agent/providers/pi/agent.ts Adds context-window polling and milestone-based emission; pollUsage and maybeEmitUsageForTurn share identical fetch-dedup-emit bodies without extraction
packages/server/src/server/agent/providers/pi/agent.test.ts New tests cover the polling feature but assert internal private state (usagePollInterval) instead of observable stream events, and some use multiple await Promise.resolve() drains
packages/server/src/server/agent/providers/pi/omp-integration.test.ts Three new E2E tests against live OMP binary; contain conditional branches inside test bodies and weak toBeDefined assertions rather than unconditional shape checks
packages/server/src/server/agent/provider-snapshot-manager.ts One-line defensive fix: optional-chains fetchCatalog to avoid a crash when the injected client doesn't implement it

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Caller
    participant PiRpcAgentSession
    participant setInterval
    participant runtimeSession

    Caller->>PiRpcAgentSession: startTurn(prompt)
    PiRpcAgentSession->>setInterval: setInterval(pollUsage, pollingIntervalMs)
    PiRpcAgentSession->>runtimeSession: prompt(text, images)

    loop every pollingIntervalMs
        setInterval->>PiRpcAgentSession: pollUsage()
        PiRpcAgentSession->>runtimeSession: getSessionStats()
        runtimeSession-->>PiRpcAgentSession: PiSessionStats
        PiRpcAgentSession->>PiRpcAgentSession: toAgentUsage() + buildUsageKey()
        alt key changed
            PiRpcAgentSession-->>Caller: emit usage_updated
        end
    end

    runtimeSession-->>PiRpcAgentSession: tool_execution_end event
    PiRpcAgentSession->>runtimeSession: getSessionStats() [maybeEmitUsageForTurn]
    runtimeSession-->>PiRpcAgentSession: PiSessionStats
    alt key changed
        PiRpcAgentSession-->>Caller: emit usage_updated
    end

    runtimeSession-->>PiRpcAgentSession: agent_end event
    PiRpcAgentSession->>PiRpcAgentSession: completeTurn() → stopUsagePolling()
    PiRpcAgentSession->>setInterval: clearInterval
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Caller
    participant PiRpcAgentSession
    participant setInterval
    participant runtimeSession

    Caller->>PiRpcAgentSession: startTurn(prompt)
    PiRpcAgentSession->>setInterval: setInterval(pollUsage, pollingIntervalMs)
    PiRpcAgentSession->>runtimeSession: prompt(text, images)

    loop every pollingIntervalMs
        setInterval->>PiRpcAgentSession: pollUsage()
        PiRpcAgentSession->>runtimeSession: getSessionStats()
        runtimeSession-->>PiRpcAgentSession: PiSessionStats
        PiRpcAgentSession->>PiRpcAgentSession: toAgentUsage() + buildUsageKey()
        alt key changed
            PiRpcAgentSession-->>Caller: emit usage_updated
        end
    end

    runtimeSession-->>PiRpcAgentSession: tool_execution_end event
    PiRpcAgentSession->>runtimeSession: getSessionStats() [maybeEmitUsageForTurn]
    runtimeSession-->>PiRpcAgentSession: PiSessionStats
    alt key changed
        PiRpcAgentSession-->>Caller: emit usage_updated
    end

    runtimeSession-->>PiRpcAgentSession: agent_end event
    PiRpcAgentSession->>PiRpcAgentSession: completeTurn() → stopUsagePolling()
    PiRpcAgentSession->>setInterval: clearInterval
Loading

Reviews (4): Last reviewed commit: "fix(server): use optional chaining for f..." | Re-trigger Greptile

Comment thread packages/server/src/server/agent/providers/pi/agent.test.ts
contextWindowReportingEnabled: true,
});
// No turn started yet — no interval should exist.
expect((session as unknown as Record<string, unknown>).usagePollInterval).toBe(null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Tests reach through the module interface into private state

Three tests assert against (session as unknown as Record<string, unknown>).usagePollInterval. This is an implementation detail — callers of PiRpcAgentSession never see this field. The module's observable contract is the stream of AgentStreamEvent values; usagePollInterval being non-null is a side effect of the interval being active, not a behavior the caller cares about.

The relevant behavior is whether a usage_updated event arrives (or doesn't) within a polling window. The "starts polling" test could instead advance fake timers by one interval, drain microtasks, and assert a usage_updated event appears on the subscriber. The "stops on close" test could assert that no further events arrive after close(). Accessing private fields via cast is flagged in the project's test discipline rules as a sign the test is asserting the wrong thing.

Rule Used: # Code Review Pattern Reference: Slop, Tests, Feat... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread packages/server/src/server/agent/providers/pi/omp-integration.test.ts Outdated
@@ -1014,7 +1032,9 @@ export class PiRpcAgentSession implements AgentSession {
this.state.thinkingLevel ??
null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 lastEmittedUsageKey carries over across turns

lastEmittedUsageKey is initialised to null once at construction and never cleared when a new turn starts. Pi's token stats are cumulative, so at the very beginning of turn 2 the stats snapshot will still equal the last snapshot from turn 1. The first poll of the new turn will compare the new key against that stale last-emission key, see no change, and silently drop the first usage_updated event for the turn.

Reset lastEmittedUsageKey = null at the top of startTurn (after setting activeTurnId) so the new turn always emits at least one update.

- Add missing vitest imports (describe, expect, test) to omp-integration.test.ts
  causing 'describe is not defined' crash on CI
- Replace conditional assertions with unconditional shape checks in
  omp-integration tests per testing discipline rules
- Wrap all three vi.useFakeTimers() tests in try/finally { vi.useRealTimers() }
  to prevent fake timer leaks corrupting subsequent tests
Comment thread packages/server/src/server/agent/providers/pi/agent.ts
theslava and others added 2 commits July 3, 2026 23:20
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.

1 participant