telemetry.ts 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. /**
  2. * Launcher-side telemetry wiring: resolve consent and send one fire-and-forget
  3. * event around each dsh-sdk command. Best-effort — never affects the command's
  4. * outcome or exit code.
  5. *
  6. * @module @deepseek-ai/dsh-scripts/telemetry
  7. */
  8. import {
  9. ConsentResolver,
  10. TelemetryReporter,
  11. buildTelemetryPayload,
  12. type ConsentDecision,
  13. } from '@deepseek-ai/dsh-telemetry'
  14. /** One command's telemetry lifecycle facts. */
  15. export interface CommandTelemetryEvent {
  16. /** The dsh-sdk command that ran. */
  17. command: string
  18. /** Project directory whose consent, `cordis.yml`, and `package.json` are read. */
  19. cwd: string
  20. /** Wall-clock duration in milliseconds. */
  21. durationMs: number
  22. /** Whether the command completed without error. */
  23. success: boolean
  24. }
  25. /** Injectable consent and delivery seams for tests. */
  26. export interface CommandTelemetryDeps {
  27. resolve?: (cwd: string) => Promise<ConsentDecision>
  28. reporter?: Pick<TelemetryReporter, 'report' | 'flush'>
  29. }
  30. /**
  31. * Resolve consent for the project and, when allowed, assemble and send one
  32. * telemetry event, draining in-flight sends before returning. Swallows every
  33. * error so telemetry can never change a command's result.
  34. * @param event - the command lifecycle facts.
  35. * @param deps - consent and delivery seams; defaults hit the real endpoint.
  36. */
  37. export async function reportCommandTelemetry(
  38. event: CommandTelemetryEvent,
  39. deps: CommandTelemetryDeps = {},
  40. ): Promise<void> {
  41. try {
  42. /* v8 ignore next -- the production ConsentResolver is exercised by the built-bin smoke */
  43. const resolve = deps.resolve ?? (cwd => new ConsentResolver().resolve(cwd))
  44. const consent = await resolve(event.cwd)
  45. if (!consent.allowed) return
  46. const payload = await buildTelemetryPayload({
  47. command: event.command,
  48. durationMs: event.durationMs,
  49. success: event.success,
  50. projectDir: event.cwd,
  51. })
  52. /* v8 ignore next -- the production TelemetryReporter is exercised by the built-bin smoke */
  53. const reporter = deps.reporter ?? new TelemetryReporter()
  54. reporter.report(payload, consent)
  55. await reporter.flush()
  56. } catch {
  57. // Telemetry is best-effort; a consent, payload, or delivery fault never reaches the command.
  58. }
  59. }