npm package: @jerry_aurora/sky-monitor-sdk
Description: AI 应用监控 SDK - 支持 SSE 流式追踪、错误监控、性能采集
Latest version: 0.2.0

# Sky Monitor SDK

轻量级 AI 应用监控 SDK，核心 gzip 仅 1.4KB。

## 为什么需要它？

传统监控 SDK（Sentry、LogRocket）针对通用 Web 应用设计，无法满足 AI 对话场景的特殊需求：

| 痛点 | 传统方案 | Sky Monitor |
|-----|---------|-------------|
| SSE 流式响应 | 只能监控请求开始/结束 | 完整生命周期追踪（TTFB/TTLB/阶段/卡顿） |
| Tool Calling | 无法追踪 | 工具调用链路、耗时、结果 |
| AI 生图 | 无法追踪 | 图片加载状态、尺寸、耗时 |
| 对话重试 | 丢失关联 | traceId 串联重试链路 |
| 包体积 | 50KB+ | 核心 1.4KB，按需加载 |

## 核心能力

```
┌─────────────────────────────────────────────────────────────┐
│  AI 对话追踪    SSE 生命周期 · 阶段拆分 · Tool Calling · 卡顿检测  │
├─────────────────────────────────────────────────────────────┤
│  错误监控      JS 错误 · Promise 拒绝 · 资源加载失败 · 去重        │
├─────────────────────────────────────────────────────────────┤
│  性能采集      Web Vitals (FCP/LCP/CLS/INP) · HTTP 请求耗时      │
├─────────────────────────────────────────────────────────────┤
│  智能上报      优先级队列 · 按类型差异化策略 · 离线缓存 · 指数退避重试  │
├─────────────────────────────────────────────────────────────┤
│  会话录制      基于 rrweb · 问题复现 · 隐私脱敏                    │
└─────────────────────────────────────────────────────────────┘
```

## 安装

```bash
npm install @sky-monitor/sdk
```

## 快速接入

```typescript
import { createMonitorWithPreset } from '@sky-monitor/sdk'

const monitor = createMonitorWithPreset({
  appId: 'my-ai-app',
  endpoint: '/api/monitor',      // 上报地址
  preset: 'production',          // 预设模式
})

monitor.setContext({ userId: 'user_123', version: '1.0.0' })
```

| 预设 | 上报模式 | 采样率 | 插件 |
|-----|---------|-------|------|
| development | immediate | 100% | 全部 |
| production | batch | 10% | 核心 |
| minimal | batch | 5% | 仅错误 |

## 全量配置

对象配置方式，插件按 `priority` 自动排序，无需关心注册顺序：

```typescript
import { createMonitorWithPreset } from '@sky-monitor/sdk'

const monitor = createMonitorWithPreset({
  // ===== 基础配置 =====
  appId: 'my-ai-app',
  endpoint: '/api/monitor',
  debug: true,
  
  // ===== 启用的插件 =====
  plugins: ['error', 'performance', 'fetch', 'xhr', 'session', 'dedupe', 'sampling'],
  
  // ===== 传输配置 =====
  transport: {
    mode: 'batch',
    typeConfig: {
      js_error: 'immediate',
      promise_error: 'immediate',
      web_vital: 'batch',
      http_request: 'throttle',
    },
    batchSize: 10,
    flushInterval: 5000,
    throttleInterval: 1000,
    maxRetries: 3,
    baseRetryDelay: 1000,
    maxRetryDelay: 30000,
  },
  
  // ===== 采样配置 =====
  sampling: {
    rate: 0.1,
    mode: 'session',
    typeRates: { web_vital: 0.5 },
    alwaysSample: ['js_error'],
  },
  
  // ===== 错误监控配置 =====
  error: {
    captureConsoleError: false,
    dedupeWindow: 5000,
    ignoreErrors: [/ResizeObserver/],
    ignoreUrls: [/chrome-extension/],
  },
  
  // ===== Fetch 拦截配置 =====
  fetch: {
    includeUrls: [/\/api\//],
    excludeUrls: [/\/health/],
  },
  
  // ===== XHR 拦截配置 =====
  xhr: {
    includeUrls: [/\/api\//],
    excludeUrls: [/\/health/],
  },
  
  // ===== 去重配置 =====
  dedupe: {
    windowMs: 5000,
  },
  
  // ===== 离线队列配置 =====
  offlineQueue: {
    maxRetries: 3,
    retryInterval: 10000,
  },
  
  // ===== 会话录制配置（需单独导入）=====
  // replay: { ... }  // 见下方 "会话录制" 章节
})

monitor.setContext({ userId: 'user_123', version: '1.0.0' })
```

## 手动注册插件

需要更细粒度控制时，可以手动注册插件（插件有 `priority` 自动排序）：

```typescript
import { Monitor } from '@sky-monitor/sdk'
import { ErrorPlugin } from '@sky-monitor/sdk/plugins/error'
import { FetchPlugin } from '@sky-monitor/sdk/plugins/fetch'
import { TransportPlugin } from '@sky-monitor/sdk/plugins/transport'

const monitor = new Monitor({ appId: 'my-ai-app', debug: true })

// 注册顺序无关，按 priority 自动排序
monitor.use(new TransportPlugin({ endpoint: '/api/monitor' }))
monitor.use(new ErrorPlugin({ ignoreErrors: [/ResizeObserver/] }))
monitor.use(new FetchPlugin({ includeUrls: [/\/api\//] }))
```

## 会话录制（Replay）

Replay 插件内置 rrweb（~182KB），单独打包以保持主包体积：

```typescript
import { createMonitorWithPreset } from '@sky-monitor/sdk'
import { ReplayPlugin } from '@sky-monitor/sdk/plugins/replay'

const monitor = createMonitorWithPreset({
  appId: 'my-ai-app',
  endpoint: '/api/monitor',
})

// 单独注册 Replay 插件
monitor.use(new ReplayPlugin({
  checkoutEveryNms: 30000,
  bufferSize: 1000,
  flushInterval: 10000,
  blockSelector: '.no-record',
  maskInputOptions: { password: true },
}))
```

## 配置类型定义

```typescript
// ===== 完整配置 =====
interface MonitorConfig {
  appId: string                   // 应用标识（必填）
  endpoint?: string               // 上报地址，默认 '/api/monitor'
  debug?: boolean                 // 调试模式
  preset?: 'development' | 'production' | 'minimal'
  plugins?: PluginName[]          // 启用的插件
  
  // 插件配置
  transport?: TransportPluginOptions
  sampling?: SamplingPluginOptions
  error?: ErrorPluginOptions
  fetch?: FetchPluginOptions
  xhr?: XHRPluginOptions
  dedupe?: DedupePluginOptions
  offlineQueue?: OfflineQueuePluginOptions
  replay?: ReplayPluginOptions
}

// ===== 传输配置 =====
interface TransportPluginOptions {
  mode?: 'immediate' | 'batch' | 'throttle'
  typeConfig?: Record<string, 'immediate' | 'batch' | 'throttle'>
  batchSize?: number              // 默认 10
  flushInterval?: number          // 默认 5000ms
  throttleInterval?: number       // 默认 1000ms
  maxRetries?: number             // 默认 3
  baseRetryDelay?: number         // 默认 1000ms
  maxRetryDelay?: number          // 默认 30000ms
}

// ===== 采样配置 =====
interface SamplingPluginOptions {
  rate?: number                   // 默认采样率 (0-1)
  mode?: 'random' | 'session' | 'user'
  typeRates?: Record<string, number>
  alwaysSample?: string[]
}

// ===== 错误监控配置 =====
interface ErrorPluginOptions {
  captureConsoleError?: boolean   // 默认 false
  dedupeWindow?: number           // 默认 5000ms
  ignoreErrors?: RegExp[]
  ignoreUrls?: RegExp[]
}

// ===== 网络请求配置 =====
interface FetchPluginOptions {
  includeUrls?: RegExp[]
  excludeUrls?: RegExp[]
}

interface XHRPluginOptions {
  includeUrls?: RegExp[]
  excludeUrls?: RegExp[]
}

// ===== 去重配置 =====
interface DedupePluginOptions {
  windowMs?: number               // 默认 5000ms
}

// ===== 离线队列配置 =====
interface OfflineQueuePluginOptions {
  maxRetries?: number             // 默认 3
  retryInterval?: number          // 默认 10000ms
}

// ===== 会话录制配置 =====
interface ReplayPluginOptions {
  checkoutEveryNms?: number       // 默认 30000ms
  bufferSize?: number             // 默认 1000
  flushInterval?: number          // 默认 10000ms
  blockSelector?: string
  maskInputOptions?: { password?: boolean; email?: boolean }
}

// ===== AI 对话追踪 =====
interface TraceOptions {
  aiMessageId: string             // AI 消息 ID（必填）
  previousTraceId?: string        // 重试时的前一个 traceId
  stallThreshold?: number         // 卡顿检测阈值（毫秒）
}
```

## AI 对话追踪使用

```typescript
const trace = monitor.createTrace({ aiMessageId: 'msg_123', stallThreshold: 5000 })

trace.start()                     // SSE 开始
trace.firstChunk()                // 首字节 (TTFB)
trace.phaseStart('thinking')      // 阶段开始
trace.phaseEnd('thinking')
trace.toolStart('web_search', { query: 'AI' })
trace.toolEnd('web_search', { success: true, resultCount: 5 })
trace.complete()                  // SSE 完成 (TTLB)
```

## 后端接收示例

```typescript
// app/api/monitor/route.ts
import { NextRequest, NextResponse } from 'next/server'

interface MonitorEvent {
  id: string
  type: string                    // 'js_error' | 'http_request' | 'sse_complete' | ...
  timestamp: number
  data: {
    // http_request
    url?: string
    method?: string
    status?: number
    duration?: number             // HTTP 请求耗时（毫秒）
    // js_error
    message?: string
    stack?: string
    // sse_complete
    ttfb?: number
    ttlb?: number
    // web_vital
    name?: string
    value?: number
    [key: string]: unknown
  }
  context: {
    appId: string
    sessionId?: string
    userId?: string
    url?: string
  }
}

export async function POST(req: NextRequest) {
  const events: MonitorEvent[] = await req.json()
  await db.monitorEvents.createMany({ data: events })
  return NextResponse.json({ success: true })
}
```

## 包体积

| 模块 | 原始 | Gzip |
|-----|------|------|
| 核心 | 4.1 KB | 1.4 KB |
| 全量 UMD | 32 KB | 8.2 KB |
| Replay UMD（含 rrweb） | 182 KB | 58 KB |

> Replay 插件内置 rrweb，需单独导入

## CDN 使用（UMD）

```html
<!-- 核心 SDK -->
<script src="https://cdn.example.com/sky-monitor.umd.js"></script>
<script>
  const monitor = SkyMonitor.createMonitor({ appId: 'my-app' })
  monitor.setContext({ userId: 'user_123' })
</script>

<!-- 需要 Replay 时额外加载 -->
<script src="https://cdn.example.com/sky-monitor-replay.umd.js"></script>
<script>
  monitor.use(new SkyMonitorReplay.ReplayPlugin({
    blockSelector: '.no-record'
  }))
</script>
```

## License

MIT