| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 |
- /**
- * SessionTelemetryBackend Service Definition for the DeepSeek Harness.
- *
- * This package owns the CAPTURE side of session-event reporting — the complete
- * one-record-per-event ledger mirror, what records carry, when
- * they are captured (adoption, the per-append firehose, lifecycle
- * forwarding), live versus on-demand canonical-log capture, and the HMR
- * cursor. Everything downstream of
- * {@link SessionTelemetryBackend.emit} — batching, retry, queueing, and loss policy — is the
- * reporting SDK's territory and is deliberately not modelled here. The
- * design and its trade-offs are pinned in
- * .agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md.
- *
- * @module @deepseek-ai/dsh-session-telemetry
- */
- import { Context, Service } from '@deepseek-ai/cordis'
- declare module '@deepseek-ai/cordis' {
- interface Context {
- sessionTelemetry: SessionTelemetryBackend
- }
- interface Events {
- /**
- * Transform one outbound record before it reaches the backend. This
- * waterfall is the Service Definition's redaction extension point. It ships NO rules
- * of its own: the
- * innermost `next()` passes the record through unchanged, and with no
- * listener mounted records reach the backend as captured, so exported
- * data is exactly as clean as the rules a deployment mounts. Listeners
- * stack by transforming `next()`'s return value; returning without
- * `next()` replaces everything beneath. Dispatched synchronously on the
- * capture hot path inside the coordinator's containment: a throwing
- * listener withholds that one record (fail-closed) and never reaches the
- * agent loop. Live capture dispatches at append time; on-demand capture
- * dispatches while reading the canonical log. Redaction applies to the
- * exported copy only; the canonical session log is never rewritten.
- * @param record - the candidate record, already the coordinator's own deep
- * copy; listeners return a (possibly new) record and must not mutate it.
- * @mode waterfall
- */
- 'session-telemetry/record'(record: SessionTelemetryRecord, next: () => SessionTelemetryRecord): SessionTelemetryRecord
- }
- }
- /**
- * Severity of a telemetry record, pre-mapped at capture so a receiver can
- * alert with zero configuration: `error` for events whose own outcome flag
- * says so (the tool-result block's `isError`, `turn/end` error reasons) and for
- * `agent-error` operational records. Captured events otherwise default to
- * `info`; `warn` remains available to `session-telemetry/record` policies and
- * backends.
- */
- export type SessionTelemetrySeverity = 'info' | 'warn' | 'error'
- /**
- * One logical record handed to a backend — the capture contract's whole outbound
- * vocabulary. Ledger records mirror session-log events one-to-one;
- * operational records (`channel: 'ops'`) carry the two signals with no log
- * home (`agent-error`, `shutdown`) and deliberately omit `event.seq`-style
- * identity so they can never be mistaken for ledger rows.
- */
- export interface SessionTelemetryRecord {
- /** Ledger (session-log mirror) or ops (operational signal) channel; backends keep the two under separate instrumentation scopes. */
- channel: 'ledger' | 'ops'
- /** Unix epoch milliseconds — the source event's append time for ledger records, the emission time for ops records. */
- time: number
- /** Pre-mapped alerting severity; see {@link SessionTelemetrySeverity}. */
- severity: SessionTelemetrySeverity
- /**
- * Identity attributes, deliberately minimal: ledger records carry
- * `session.id`, `session.format_version`, `event.type`, `event.seq`, plus optional
- * `session.cwd` / `session.parent_id`; a seeded Session also carries
- * `session.seed_length` from its exact inherited event count;
- * ops records carry `telemetry.op`, `session.id`, and (for `agent-error`)
- * `agent.id`, `turn`, `step`, `error.name`. Anything recoverable from the
- * body is intentionally NOT duplicated here.
- */
- attributes: Record<string, string | number>
- /**
- * The complete payload: a deep copy of the session event's `data` for
- * ledger records (JSON-serializable by `Session.append`'s own
- * validation), or the op payload for ops records. Never mutated after
- * handoff.
- */
- body: unknown
- }
- /**
- * The minimum backend contract the coordinator requires. {@link SessionTelemetryBackend} is
- * its service-registered form; tests compose the coordinator with a bare
- * implementation of this interface.
- */
- export interface SessionTelemetrySink {
- /**
- * Hand one record to the backend's pipeline. MUST be a non-blocking
- * enqueue — the coordinator calls this synchronously from the
- * `session/event` hot path or an explicit canonical-log capture, so anything
- * slower than a queue push would tax the agent loop or feedback handling.
- * Errors thrown here are contained by the coordinator and logged; they
- * never reach the loop.
- * @param record - the logical record to report; owned by the backend after the call.
- */
- emit(record: SessionTelemetryRecord): void
- /**
- * Optional hint that a turn ended. A backend may forward it to its SDK's
- * flush so records are exported after each turn. Called
- * fire-and-forget; implementations must not block and must not throw
- * meaningfully (the coordinator contains exceptions). Most backends should
- * leave this unimplemented and let their SDK's own batching cadence govern
- * export timing: a backend that does implement it owns the interaction
- * between its concurrent flushes and {@link shutdown}'s drain (the OTel
- * backend leaves it unimplemented for exactly that hazard — see the
- * revival Agent Note).
- */
- flush?(): void
- /**
- * Forward the fiber's disposal to the SDK: flush whatever is queued and
- * reach quiescence, per the SDK's own shutdown contract. Everything
- * emitted before this call must still be delivered — including records
- * enqueued while a {@link flush} hint is in flight, so a backend whose SDK
- * guards against concurrent flushes orders behind the outstanding one (the
- * coordinator emits its dispose-time `shutdown` markers immediately before
- * calling this). Awaited by the coordinator's dispose; a rejection is
- * logged as a warning and never fails application teardown.
- * The coordinator captures dispose-time shutdown markers immediately before
- * this call for live capture; on-demand capture creates no ops records.
- * @returns resolves when the backend's pipeline has quiesced.
- */
- shutdown(): Promise<void>
- }
- /**
- * Deployment-selected session-sharing mode, not confirmation of SDK delivery.
- */
- export type SessionTelemetrySharingStatus = 'full' | 'feedback-only' | 'disabled'
- /**
- * Loadable form of the backend contract: one implementation per context —
- * the cordis `Service` registration under the `telemetry` key throws on a
- * duplicate, cordis' standard behavior. A backend composes a
- * {@link SessionTelemetryCoordinator} in its constructor to install the capture side.
- */
- export abstract class SessionTelemetryBackend extends Service implements SessionTelemetrySink {
- constructor(ctx: Context) {
- super(ctx, 'sessionTelemetry')
- }
- /**
- * Deployment-selected sharing mode, independent of SDK delivery.
- */
- abstract readonly sharing: SessionTelemetrySharingStatus
- /**
- * See {@link SessionTelemetrySink.emit} — that declaration is the contract's one home.
- * @param record - the logical record to report; owned by the backend after the call.
- */
- abstract emit(record: SessionTelemetryRecord): void
- /** See {@link SessionTelemetrySink.flush}. */
- flush?(): void
- /**
- * See {@link SessionTelemetrySink.shutdown}.
- * @returns resolves when the backend's pipeline has quiesced.
- */
- abstract shutdown(): Promise<void>
- }
- export { SessionTelemetryCoordinator, type SessionTelemetryCapture, type SessionTelemetryCaptureOptions } from './coordinator.ts'
|