File size: 1,604 Bytes
76289e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import {
  areDiagnosticsEnabledForProcess,
  emitTrustedDiagnosticEvent,
} from "../infra/diagnostic-events.js";
import { markDiagnosticRunProgress } from "./diagnostic-run-activity.js";

const MODEL_CALL_STREAM_PROGRESS_INTERVAL_MS = 30_000;

/** Canonical progress reason for model output observed on a live backend stream. */
const MODEL_CALL_STREAM_PROGRESS_REASON = "model_call:stream_progress";

export type ModelCallStreamProgressTarget = {
  runId: string;
  sessionKey?: string;
  sessionId?: string;
};

// Refresh recovery on every chunk, but throttle public events. Owner-bound
// callbacks also reject late output without refreshing a replacement's clock.
export function createModelCallStreamProgressReporter(
  recordProgress?: () => boolean,
): (target: ModelCallStreamProgressTarget) => void {
  let lastEmittedAtMs: number | undefined;
  return (target) => {
    if (recordProgress && !recordProgress()) {
      return;
    }
    if (!areDiagnosticsEnabledForProcess()) {
      return;
    }
    const fields = {
      runId: target.runId,
      ...(target.sessionKey ? { sessionKey: target.sessionKey } : {}),
      ...(target.sessionId ? { sessionId: target.sessionId } : {}),
      reason: MODEL_CALL_STREAM_PROGRESS_REASON,
    };
    if (!recordProgress) {
      markDiagnosticRunProgress(fields);
    }
    const now = Date.now();
    if (
      lastEmittedAtMs !== undefined &&
      now - lastEmittedAtMs < MODEL_CALL_STREAM_PROGRESS_INTERVAL_MS
    ) {
      return;
    }
    lastEmittedAtMs = now;
    emitTrustedDiagnosticEvent({ type: "run.progress", ...fields });
  };
}