# SMH JS SDK

腾讯云智能媒资托管（Smart Media Hosting，SMH）浏览器端 JavaScript/TypeScript SDK，为开发者提供便捷的浏览器端接口来访问和管理腾讯云 SMH 服务，支持文件上传（含秒传、分片、断点续传）、下载、短链、目录管理、搜索与增量同步等完整功能。

> 🤖 **使用 AI / Agent 接入本 SDK？** 请阅读 **[sdk-docs/AGENTS.md](./sdk-docs/AGENTS.md)**，这是为 Coding Agent（Codex / Claude / CodeBuddy 等）定制的使用手册，可帮助 Agent 正确选择 SDK 入口并生成代码。

## 前提条件

- 浏览器需支持 File API、ArrayBuffer、BigInt、WebAssembly
- 已开通腾讯云智能媒资托管服务
- 已获取媒体库 ID（libraryId）和访问令牌（accessToken，由后端服务签发）

## 安装

使用 npm：

```bash
npm install smh-js-sdk
```

使用 yarn：

```bash
yarn add smh-js-sdk
```

使用 pnpm：

```bash
pnpm add smh-js-sdk
```

## 快速开始

### 1. 引入 SDK

```typescript
// ES Module（推荐）
import { SMHClient } from 'smh-js-sdk'

// CommonJS
const { SMHClient } = require('smh-js-sdk')
```

### 2. 初始化 SMHClient

```typescript
const client = new SMHClient({
  basePath: 'https://smhxxx.api.tencentsmh.cn', // 专属域名（推荐）
  libraryId: 'your-library-id',
  spaceId: 'your-space-id',
  accessToken: 'your-access-token',  // 由后端服务创建
  maxRetries: 3,  // 可选，请求失败重试次数，默认 3
  timeout: 30000, // 可选，请求超时时间（毫秒），默认 30000
})
```

> **basePath 专属域名获取方式**：在 [腾讯云智能媒资托管控制台](https://console.cloud.tencent.com/smh) 创建媒体库后，控制台会展示为您生成的专属域名（如 `smhxxx.api.tencentsmh.cn`）。**强烈建议**将 `basePath` 设置为您的专属域名，以获得更好的访问性能和稳定性。

> **安全提示**：`librarySecret` 属于敏感凭据，**切勿在浏览器端暴露**。创建访问令牌（`createToken`）应在后端服务中完成，前端仅使用后端签发的 `accessToken`。

初始化后，后续调用 API 时无需每次都传入 `libraryId`、`spaceId`、`accessToken`，SDK 会自动注入。

### 3. AccessToken 续期

`accessToken` 有有效期限制（默认 24 小时），过期后需要续期。

#### 方式一：自动续期（推荐）

通过 `onTokenRefresh` 回调实现自动续期，token 过期时 SDK 自动调用回调获取新 token 并重试请求，业务侧无需关心续期时机：

```typescript
const client = new SMHClient({
  basePath: 'https://smhxxx.api.tencentsmh.cn',
  libraryId: 'your-library-id',
  spaceId: 'your-space-id',
  accessToken: 'your-access-token',
  onTokenRefresh: async () => {
    // 调用后端服务获取新 token
    const res = await fetch('/api/refresh-smh-token')
    const { accessToken } = await res.json()
    return accessToken
  }
})
```

也可以运行时动态设置：

```typescript
client.setOnTokenRefresh(async () => {
  const { accessToken } = await myBackend.refreshToken()
  return accessToken
})
```

> 多个并发请求同时遇到 token 过期时，只会触发一次续期回调，其余请求等待同一结果。

#### 方式二：手动续期

```typescript
const renewResponse = await client.token.renewToken({
  libraryId: 'your-library-id',
  accessToken: accessToken,
})

const newAccessToken = renewResponse.data.accessToken

// 更新默认 accessToken
client.setDefaultAccessToken(newAccessToken)
```

> **注意**：手动续期方式建议在业务逻辑中提前进行续期以避免请求失败。

### 4. 使用 API

```typescript
// 列出目录内容
const contents = await client.directory.listDirectory({
  filePath: '/',
  limit: 100,
})
console.log('目录内容:', contents.data)

// 创建目录
await client.directory.createDirectory({
  filePath: '/new-folder',
})

// 删除文件
await client.file.deleteFile({
  filePath: '/path/to/file.txt',
})

// 搜索文件
const searchResult = await client.search.searchFs({
  searchFsRequest: { keywords: ['test'] },
})

// 获取短链形式的下载/预览链接
const fileInfo = await client.file.infoFile({
  filePath: '/path/to/file.pdf',
  info: 1,
  withShortLink: 1,
  period: 3600, // 秒，范围 60~7200
})
console.log('短链:', fileInfo.data.cosUrl)

// 自然语言语义检索文档
const aiSearchResult = await client.search.searchAI({
  limit: 10,
  searchAIRequest: {
    type: 'text',
    keywords: '查找关于季度营收分析的材料',
    inExtnames: ['.pdf', '.docx'],
  },
})
```

#### 错误处理

```typescript
import { SMHError, ErrorCode, ServerErrorCode } from 'smh-js-sdk'

try {
  await client.directory.listDirectory({ filePath: '/' })
} catch (error) {
  if (error instanceof SMHError) {
    // error.message 已是友好文案，可直接展示给用户
    alert(error.message)

    // 按 SDK 错误码做分支
    switch (error.code) {
      case ErrorCode.NETWORK_ERROR:
        console.warn('网络异常，请检查连接')
        break
      case ErrorCode.SERVER_ERROR:
        console.warn('服务器异常，请稍后重试')
        break
      case ErrorCode.OPERATION_FAILED:
        // 可进一步按服务端错误码细分
        const serverCode = error.response?.serverCode
        if (serverCode === ServerErrorCode.NoPermission) {
          console.warn('没有权限')
        }
        break
    }

    // 排障信息
    console.log('status:', error.status, 'reqId:', error.reqId)
  }
}
```

### 5. 文件上传

```typescript
const uploader = client.createUploadTask({
  filePath: '/remote/path/file.txt',
  file: fileInput.files[0],       // 浏览器 File 对象
  enableInstantUpload: true,       // 启用秒传
  chunkSize: 5,                    // 分块大小 5MB
  parallel: 2,                     // 2 并发

  onStateChange: (checkpoint, state, error) => {
    console.log('状态:', state)    // start → computing_hash → created → running → success
  },
  onProgress: (info) => {
    console.log(`[${info.state}] 进度: ${info.progress}%, 速度: ${info.speed} B/s`)
  },
  onPartComplete: (checkpoint, partInfo) => {
    console.log(`分片 ${partInfo.part_number} 完成`)
  },
})

await uploader.start()

// 暂停 / 恢复 / 取消
await uploader.pause()
await uploader.start()   // 恢复（自动断点续传）
await uploader.cancel()
```

#### 上传错误处理

```typescript
const uploader = client.createUploadTask({
  filePath: '/remote/path/file.txt',
  file: fileInput.files[0],

  onStateChange: (checkpoint, state, error) => {
    if (state === 'error' && error) {
      // error 即 SMHError，message 已是友好文案
      console.error('上传失败:', error.message)
      console.log('错误码:', error.code)       // 如 'UploadFailed'
      console.log('HTTP 状态:', error.status)   // 如 400
      console.log('服务端错误码:', error.response?.serverCode) // 如 'QuotaLimitReached'
      console.log('请求 ID:', error.reqId)      // 用于排障
    }
  },
})

await uploader.start()
```

### 6. 文件下载

#### 方式一：浏览器 URL 下载（推荐）

通过 `<a>` 标签触发浏览器原生下载，不占用内存，适合任意大小的文件：

```typescript
await client.downloadByUrl({
  filePath: '/remote/path/file.pdf',
  fileName: '自定义文件名.pdf',  // 可选，不传则使用远端文件名
})
```

#### 方式二：内存下载

文件内容下载到内存中（Blob），适合需要二次处理的场景：

```typescript
const downloader = client.createDownloadTask({
  filePath: '/remote/path/file.txt',
  chunkSize: 5,
  parallel: 2,

  onStateChange: (checkpoint, state, error) => {
    console.log('状态:', state)
  },
  onProgress: (info) => {
    console.log(`[${info.state}] 进度: ${info.progress}%`)
  },
})

const blob = await downloader.startAndGetBlob()  // 返回 Blob 对象

// 暂停 / 恢复 / 取消
await downloader.pause()
await downloader.start()   // 恢复（自动断点续传）
await downloader.cancel()
```

#### 下载错误处理

```typescript
const downloader = client.createDownloadTask({
  filePath: '/remote/path/file.txt',

  onStateChange: (checkpoint, state, error) => {
    if (state === 'error' && error) {
      console.error('下载失败:', error.message)
      console.log('错误码:', error.code)       // 如 'DownloadFailed'
      console.log('HTTP 状态:', error.status)
      console.log('服务端错误码:', error.response?.serverCode)
      console.log('请求 ID:', error.reqId)
    }
  },
})

// 方式一：start() 不抛异常，错误走 onStateChange
await downloader.start()

// 方式二：startAndGetBlob() 会抛异常，需要 catch
try {
  const blob = await downloader.startAndGetBlob()
} catch (error) {
  if (error instanceof SMHError) {
    alert(error.message)
  }
}
```

### 7. 文件详情与短链

通过 `file.infoFile` 可以获取文件详情、下载/预览链接。设置 `withShortLink: 1` 后，返回的 `cosUrl` 会替换为短链形式；`period` 用于指定链接有效期，单位秒，取值范围为 `60 ~ 7200`。

```typescript
const res = await client.file.infoFile({
  filePath: '/remote/path/file.pdf',
  info: 1,
  withShortLink: 1,
  period: 3600,
})

console.log('下载/预览短链:', res.data.cosUrl)
```

> 短链下载/预览链接属于文件信息能力；如果需要对外分享、提取码、分享权限、分享列表等能力，请使用 `client.share`。

### 8. 搜索

SDK 提供三类搜索入口：

- `searchFs`：文件名搜索和正文全文关键字搜索，`keywords` 是字符串数组。
- `searchAI`：混合检索/语义检索，支持文搜文档和文搜图，`keywords` 是单个自然语言字符串。
- `searchFsStats`：对搜索结果做聚合统计，如 count、group、sum、distinct、min、max、average。

```typescript
// 文件名搜索
const filenameRes = await client.search.searchFs({
  limit: 20,
  searchFsRequest: {
    type: 'filename',
    keywords: ['合同', '报告'],
  },
})

// 正文全文关键字搜索
const contentRes = await client.search.searchFs({
  limit: 20,
  searchFsRequest: {
    type: 'filecontent',
    keywords: ['季度营收', '分析结果'],
  },
})

// 混合检索：文搜文档
const semanticRes = await client.search.searchAI({
  limit: 10,
  searchAIRequest: {
    type: 'text',
    keywords: '查找关于季度营收分析和经营风险的材料',
    inExtnames: ['.pdf', '.docx'],
  },
})

// 混合检索：文搜图
const imageRes = await client.search.searchAI({
  limit: 20,
  searchAIRequest: {
    type: 'pic',
    keywords: '蓝天白云的户外活动照片',
    inExtnames: ['.jpg', '.png'],
  },
})
```

> `searchAI` 需服务端开通白名单能力；不支持 `marker` 翻页，只通过 `limit` 控制返回数量。

### 9. 压缩包预览与解压

SDK 支持在不下载到本地的情况下，直接预览压缩包内容或将其解压到网盘指定目录。

> 两个能力都需要在 library 级别开启 `enableFileUncompress` 功能；`filePath` 必须指向压缩包文件（非目录）。

#### 压缩包在线预览（同步）

`previewZipFile` 在不解压的情况下列出压缩包内的文件信息（名称、大小、修改时间等），为同步请求，直接返回结果。

```typescript
// 扁平列表格式（默认）
const res = await client.file.previewZipFile({
  filePath: '/archive.zip',
  zipPreview: 1,        // 固定值 1
  format: 'flat',       // 可选：flat（默认）/ tree（树形结构）
  // password: '加密包密码',  // 可选，非加密包传入会被忽略
})

console.log('文件数量:', res.data.fileNumber)
console.log('是否被截断:', res.data.isTruncated)   // 最多预览前 1000 个文件
console.log('内容列表:', res.data.contents)        // contents 里的 key 可用于选择性解压
```

> 预览支持格式：zip、tar、gz、7zip、rar（**apk 仅支持解压、不支持预览**）；压缩包内文件数最多 1000 个，超出会截断（`isTruncated` 为 `true`）。

#### 在线解压缩（异步）

`uncompressFile` 将压缩包解压到网盘指定目录，为**异步任务**，提交后返回 `taskId`，需通过任务查询接口轮询进度。

```typescript
// 整包解压到指定目录
const res = await client.file.uncompressFile({
  filePath: '/archive.zip',
  uncompress: 1,                    // 固定值 1
  uncompressFileRequest: {
    targetPath: '/extracted/',      // 必填，目标目录须已存在
  },
  conflictResolutionStrategy: 'rename',  // 可选：rename（默认）/ overwrite / ask
})

const taskId = res.data.taskId
console.log('解压任务已提交，taskId:', taskId)

// 轮询任务状态，结果在 fileUncompressResult 字段
const task = await client.task.queryTaskV2({ taskId })
console.log('任务状态:', task.data.status)
console.log('解压结果:', task.data.fileUncompressResult)

// 选择性解压（仅解压指定文件/目录）
await client.file.uncompressFile({
  filePath: '/archive.zip',
  uncompress: 1,
  uncompressFileRequest: {
    targetPath: '/extracted/',
    selectedFilePaths: [          // 路径取自 previewZipFile 返回的 key
      'archive/readme.txt',
      'archive/subdir/',          // 目录路径需以 / 结尾
    ],
  },
})
```

> 解压支持格式：zip、tar、gz、7zip、rar、apk；支持选择性解压、跨空间解压（`targetSpaceId`，需 admin 权限）、加密包解压（`password`）。`targetPath` 目录必须已存在，否则返回 `DirectoryNotFound`。

### 10. 在线文档编辑

`officeEdit` 打开在线文档编辑入口，返回包含【文档服务】SDK 的 HTML 页面，用户可在浏览器中直接编辑文档。

> 该功能需在 library 级别开白 `enableDocEdit`，未开启时调用返回 `DocEditNotEnabled`。支持 Word / Excel / PPT / PDF 系列格式，文件不超过 200MB。

```typescript
// 同步接口：返回编辑器 HTML 页面（字符串，非 JSON）
const editRes = await client.file.officeEdit({
  filePath: 'foo/bar.docx',
  lang: 'zh_CN',        // 可选：语言偏好，如 zh_CN / en
})

if (editRes.status === 200) {
  // editRes.data 为 HTML，可直接嵌入 iframe 或跳转，无需 JSON.parse
  document.querySelector('iframe').srcdoc = editRes.data
}
```

> 支持格式：文字（.doc/.docx/.wps 等）、表格（.xls/.xlsx/.et 等）、演示（.ppt/.pptx/.dps 等）、PDF。常见错误：`DocEditNotEnabled`（未开白）、`FileTypeNotSupported`（类型不支持）、`FileSizeExceeded`（超 200MB）。详见 [FileApi.md](./sdk-docs/FileApi.md)。

## SMHClient 初始化参数

| 参数 | 类型 | 默认值 | 说明 |
|------|------|-------|------|
| `basePath` | string | - | API 基础路径，推荐使用专属域名 |
| `libraryId` | string | - | 媒体库 ID |
| `spaceId` | string | - | 空间 ID |
| `accessToken` | string | - | 访问令牌 |
| `maxRetries` | number | 3 | 请求失败重试次数（仅对网络错误和 5xx 错误重试） |
| `retryDelay` | number | 1000 | 重试基础延迟（毫秒），使用指数退避策略 |
| `timeout` | number | 30000 | 请求超时时间（毫秒） |
| `baseOptions` | object | - | 传递给 axios 的额外配置 |
| `onTokenRefresh` | `() => Promise<string>` | - | Token 续期回调，设置后 token 过期时自动调用并重试请求 |

## 错误处理与错误码

SDK 会将请求异常统一包装为 `SMHError`，并默认优先使用服务端错误码映射后的友好文案作为 `error.message`。通常业务侧**无需再手动做一次错误码转文案**；只有在需要多语言、自定义文案或更细粒度分流时，才建议自行处理。

### 1) 基础用法

```typescript
import {
  SMHClient,
  SMHError,
  ErrorCode,
  ServerErrorCode,
  getServerErrorMessage,
  setServerErrorMessages,
  resetServerErrorMessages,
  wrapErrorToSMHError,
} from 'smh-js-sdk'

const client = new SMHClient({
  basePath: 'https://smhxxx.api.tencentsmh.cn',
  libraryId: 'your-library-id',
  spaceId: 'your-space-id',
  accessToken: 'your-access-token',
})

try {
  await client.file.infoFile({ filePath: '/not-exist.txt', info: 1 })
} catch (error) {
  if (error instanceof SMHError) {
    // SDK 级错误码（稳定，可用于分支判断）
    console.log('code:', error.code)

    // HTTP 状态码（推荐优先读 status）
    console.log('status:', error.status)

    // 向后兼容：仍可通过 response.status 读取
    console.log('compat response.status:', error.response?.status)

    // 服务端请求 ID（用于排障）
    console.log('reqId:', error.reqId || error.response?.requestId)

    // 服务端错误码（如 LibraryNotFound / NoPermission）
    const serverCode = error.response?.serverCode as string | undefined
    console.log('serverCode:', serverCode)

    // 通常直接用 error.message 即可（SDK 已完成默认映射）
    console.log('friendlyMessage:', error.message)

    // 仅在你需要自定义兜底策略时再手动调用
    console.log('friendlyMessage(custom-fallback):', getServerErrorMessage(serverCode, '操作失败，请稍后重试'))

    if (error.code === ErrorCode.NETWORK_ERROR) {
      // 无响应的网络错误（超时/断网/DNS/连接失败等）
      console.warn('网络异常，请检查网络连接后重试')
    }

    if (error.code === ErrorCode.SERVER_ERROR) {
      // 服务端返回 5xx 错误
      console.warn('服务器异常，请稍后重试')
    }

    if (serverCode === ServerErrorCode.QuotaLimitReached) {
      console.warn('空间不足，请清理文件或扩容')
    }
  }
}

// 可按业务自定义（覆盖）服务端错误码文案
setServerErrorMessages({
  [ServerErrorCode.QuotaLimitReached]: '您的网盘空间已满，请升级套餐',
  [ServerErrorCode.NoPermission]: '无权限执行该操作，请联系管理员',
})

// 恢复为 SDK 默认文案
resetServerErrorMessages()
```

### 2) `SMHError` 关键字段

| 字段 | 类型 | 说明 |
|------|------|------|
| `code` | `ErrorCode` | SDK 统一错误码 |
| `message` | string | 错误消息（优先使用服务端友好文案） |
| `status` | number \| undefined | HTTP 状态码 |
| `reqId` | string \| undefined | 服务端请求 ID |
| `response` | object | 额外上下文（`api`、`serverCode`、`serverMessage`、`responseData` 等） |
| `response.status` | number \| undefined | 兼容旧版本读取方式 |
| `response.requestId` | string \| undefined | 兼容旧版本读取方式 |

### 3) SDK 自定义错误码（`ErrorCode`）

| 错误码 | 含义 |
|------|------|
| `FileNotFound` | 文件不存在 |
| `FileModified` | 文件已被修改 |
| `FileSizeMismatch` | 文件大小不匹配 |
| `FileCrc64Mismatch` | CRC64 校验不一致 |
| `FileTooLarge` | 文件过大 |
| `InvalidFile` | 非法文件对象 |
| `UploadFailed` | 上传失败 |
| `UploadCanceled` | 上传已取消 |
| `UploadPaused` | 上传已暂停 |
| `PartUploadFailed` | 分片上传失败 |
| `RenewUploadFailed` | 上传续期失败 |
| `DownloadFailed` | 下载失败 |
| `DownloadCanceled` | 下载已取消 |
| `DownloadPaused` | 下载已暂停 |
| `InvalidParameter` | 参数非法 |
| `NetworkError` | 网络错误（无响应的网络异常：超时、断网、DNS 等） |
| `ServerError` | 服务端错误（HTTP 5xx） |
| `RequestTimeout` | 请求超时 |
| `OperationFailed` | 通用操作失败 |

### 4) 服务端错误码（`ServerErrorCode`）

SDK 内置了完整的服务端错误码枚举和中文映射，详见 [服务端错误码文档](https://cloud.tencent.com/document/product/1339)。

### 5) 错误处理建议

- 优先按 `error.code` 做业务分支，保证逻辑稳定。
- `NetworkError` 表示客户端网络不可达（断网/超时/DNS 等），`ServerError` 表示服务端返回 5xx，两者可分别制定重试策略。
- 需要展示给用户时，**优先直接使用 `error.message`**（默认已映射）；仅在需要覆盖文案时再调用 `getServerErrorMessage(serverCode)`。
- 需要排障时，记录 `status`、`reqId`、`response.serverCode`、`response.responseData`。
- 与旧代码兼容时，可继续使用 `error.response?.status`。

### 6) 工具方法

| 方法 | 说明 |
|------|------|
| `getServerErrorMessage(serverCode, fallback?)` | 根据服务端错误码获取友好提示 |
| `setServerErrorMessages(messages)` | 批量自定义/覆盖服务端错误码文案 |
| `resetServerErrorMessages()` | 恢复为 SDK 默认的错误码文案映射 |
| `wrapErrorToSMHError(error, defaultCode, fallbackMsg, extra?)` | 将任意错误统一包装为 `SMHError`（自动提取 AxiosError 详情） |
| `newError(code, message, cause?, response?, options?)` | 手动创建 `SMHError` 实例 |
| `client.setOnTokenRefresh(callback)` | 运行时动态设置/更新 Token 续期回调 |

## 主要功能

### 上传功能

- **简单上传** — 小于 32MB 的文件直接上传
- **分片上传** — 大文件自动分块并发上传
- **秒传检测** — 通过 SHA256 链式哈希匹配服务端已有文件，跳过实际传输
- **断点续传** — 通过 checkpoint 保存/恢复上传进度
- **CRC64 校验** — 上传完成后进行数据完整性校验
- **自动续期** — 签名过期前自动续期
- **冲突策略** — 支持 `ask` / `rename` / `overwrite`

### 下载功能

- **浏览器 URL 下载** — 通过 `<a>` 标签触发浏览器原生下载，不占用内存，适合任意大小文件
- **简单下载** — 小文件流式下载
- **分片下载** — 大文件 Range 请求并发下载
- **断点续传** — 保存/恢复下载进度
- **CRC64 校验** — 下载完成后校验数据完整性
- **短链下载/预览链接** — 通过 `file.infoFile({ withShortLink: 1, period })` 获取短链形式的 `cosUrl`

### 空间管理

- 创建/删除租户空间
- 列出空间列表
- 获取空间信息（文件数量、空间大小、扩展信息）
- 获取空间内容视图
- 设置空间流量限制
- 更新空间扩展信息

### 目录管理

- 列出目录内容（支持分页）
- 创建目录
- 删除目录（支持永久删除）
- 移动/重命名目录
- 复制目录
- 检查目录状态
- 获取目录或文件详细信息
- 更新目录/文件标签
- 查询目录统计数据（子目录数、文件数、总大小，支持普通/回收站/历史版本）

### 文件管理

- **上传文件** — 简单上传、表单上传、分片上传、断点续传
- **下载文件** — 直接下载、获取文件信息、获取文件预览、获取文件封面、获取短链形式的下载/预览链接
- **压缩包处理** — 压缩包在线预览（不解压列出包内文件）、在线解压缩（解压到网盘指定目录，异步任务）
- **文件操作** — 删除、移动/重命名、复制、创建符号链接、文件转码、检查状态、根据 inode 获取文件信息、查询文件删除原因
- **增量同步** — 获取增量游标、查询增量变动日志（基于 cursor 拉取文件变更，用于增量同步场景）

### 回收站管理（RecycledApi）

- 列出回收站内容
- 恢复回收站项目（单个/批量）
- 永久删除回收站项目（单个/批量）
- 清空回收站
- 获取回收站项目信息
- 预览回收站文件
- 设置回收站生命周期

### 历史版本管理（HistoryApi）

- 列出文件历史版本
- 删除历史版本
- 设置历史版本为最新版本
- 获取/设置历史版本配置
- 清空历史版本

### 分享管理（ShareApi）

- 创建分享链接
- 删除分享
- 获取分享详情
- 获取分享链接详情
- 列出分享列表
- 搜索分享
- 更新分享设置
- 启用/禁用分享
- 验证提取码
- 列出分享文件
- 下载分享文件
- 预览分享文件
- 保存分享文件到个人空间

### 搜索功能（SearchApi）

- 文件名搜索：`searchFs` + `type=filename`
- 正文全文关键字搜索：`searchFs` + `type=filecontent`
- 混合检索/语义检索：`searchAI`，支持文搜文档 `type=text` 和文搜图 `type=pic`
- 搜索聚合统计：`searchFsStats`，支持 count、group、sum、distinct、min、max、average 等聚合

### 收藏管理（FavoriteApi）

- 收藏文件/目录
- 取消收藏
- 列出收藏列表

### 最近使用（RecentApi）

- 查看最近使用文件列表

### 批量操作（BatchApi）

- 批量复制文件/目录
- 批量移动文件/目录
- 批量删除文件/目录

### 任务管理（TaskApi）

- 查询异步任务状态
- 查询媒体库级别任务
- 查询空间级别任务

### 配额管理（QuotaApi）

- 创建配额
- 获取租户空间配额
- 获取配额详细信息
- 更新配额（按空间 ID 或配额 ID）

### 使用量统计（UsageApi）

- 查询媒体库容量信息
- 批量查询租户空间容量信息

## 上传配置项

| 参数 | 类型 | 默认值 | 说明 |
|------|------|-------|------|
| `filePath` | string | (必填) | 远端目标路径 |
| `file` | File | (必填) | 浏览器 File 对象 |
| `chunkSize` | number | 5 | 分块大小 (MB) |
| `parallel` | number | 2 | 并发数 |
| `partFileSize` | number | 32 | 分片上传阈值 (MB)，范围 1~5120 |
| `enableInstantUpload` | boolean | true | 是否启用秒传 |
| `trafficLimit` | number | - | 单链接限速 (100KB/s ~ 100MB/s) |
| `internalDomain` | 0 \| 1 | - | 是否使用内网域名生成上传链接，1 为使用，适用于同地域内网访问场景 |
| `conflictResolutionStrategy` | string | - | 冲突策略：`ask` / `rename` / `overwrite` |
| `autoCreateDir` | boolean | false | 上传到不存在的目录时是否自动创建所需父目录后重试。开启后若上传因 `DirectoryNotFound` 失败，SDK 会对目标目录调用一次 `createDirectory`（服务端自动递归创建各级父目录）并重试一次 |
| `checkpoint` | UploadCheckpoint | - | 断点续传 checkpoint |
| `verbose` | boolean | false | 详细日志 |
| `onStateChange` | function | - | 状态变更回调 |
| `onProgress` | function | - | 进度回调 |
| `onPartComplete` | function | - | 分片完成回调 |

> 通过 `client.createUploadTask()` 创建时，`libraryId`、`spaceId`、`accessToken` 会自动从 client 注入，无需手动传入。

## 下载配置项

### `downloadByUrl` 配置项

| 参数 | 类型 | 默认值 | 说明 |
|------|------|-------|------|
| `filePath` | string | (必填) | 远端文件路径 |
| `fileName` | string | - | 自定义下载文件名，不传则使用远端文件名 |
| `historyId` | string | - | 历史版本 ID，用于下载指定历史版本的文件，不传则下载最新版 |
| `internalDomain` | 0 \| 1 | - | 是否使用内网域名生成下载链接，1 为使用，适用于同地域内网访问场景 |

### `createDownloadTask` 配置项

| 参数 | 类型 | 默认值 | 说明 |
|------|------|-------|------|
| `filePath` | string | (必填) | 远端文件路径 |
| `chunkSize` | number | 5 | 分块大小 (MB) |
| `parallel` | number | 2 | 并发数 |
| `partFileSize` | number | 32 | 分块下载阈值 (MB) |
| `trafficLimit` | number | - | 单链接限速 |
| `historyId` | string | - | 历史版本 ID，用于下载指定历史版本的文件，不传则下载最新版 |
| `internalDomain` | 0 \| 1 | - | 是否使用内网域名生成下载链接，1 为使用，适用于同地域内网访问场景 |
| `checkpoint` | DownloadCheckpoint | - | 断点续传 checkpoint |
| `verbose` | boolean | false | 详细日志 |
| `onStateChange` | function | - | 状态变更回调 |
| `onProgress` | function | - | 进度回调 |
| `onPartComplete` | function | - | 分片完成回调 |

> 通过 `client.createDownloadTask()` 或 `client.downloadByUrl()` 创建时，`libraryId`、`spaceId`、`accessToken` 会自动从 client 注入，无需手动传入。

## Demo 项目

本仓库 [`demo/`](./demo/) 目录下提供了可直接运行的浏览器端示例：

### demo/queue — 上传队列 Demo

基于 **Vite + React + smh-js-sdk** 的极简上传队列管理示例，演示大文件分片上传、秒传、断点续传、并发调度、进度与状态管理等能力。

```bash
cd demo/queue
npm install
npm run dev
```

启动后浏览器会自动打开 <http://localhost:5191>。凭证（`basePath` / `libraryId` / `spaceId` / `accessToken`）可在 `src/App.jsx` 顶部 `DEFAULT_CRED` 中填写。

更多说明与架构解析见 [demo/queue/README.md](./demo/queue/README.md) 与 [demo/queue/ARCHITECTURE.md](./demo/queue/ARCHITECTURE.md)。

### 演示功能

- 文件上传（分片、秒传、暂停/续传）
- 并发调度、进度与速度统计
- 批量操作：清除已完成 / 暂停全部 / 开始全部 / 删除全部
- 状态过滤：全部 / 上传中 / 失败 / 已完成


## 文档

### SDK 文档

- 🤖 **[AGENTS.md — AI / Agent 使用手册](./sdk-docs/AGENTS.md)**（推荐给 Coding Agent 阅读）
- [快速开始](./sdk-docs/Started.md)

#### API 模块

- [DirectoryApi — 目录管理](./sdk-docs/DirectoryApi.md)
- [FileApi — 文件管理（含短链、压缩包预览/解压、增量同步 Delta）](./sdk-docs/FileApi.md)
- [SpaceApi — 空间管理](./sdk-docs/SpaceApi.md)
- [BatchApi — 批量操作](./sdk-docs/BatchApi.md)
- [RecycledApi — 回收站管理](./sdk-docs/RecycledApi.md)
- [HistoryApi — 历史版本管理](./sdk-docs/HistoryApi.md)
- [SearchApi — 搜索（文件名/全文/混合检索/聚合统计）](./sdk-docs/SearchApi.md)
- [FavoriteApi — 收藏](./sdk-docs/FavoriteApi.md)
- [RecentApi — 最近使用](./sdk-docs/RecentApi.md)
- [TaskApi — 任务管理](./sdk-docs/TaskApi.md)
- [QuotaApi — 配额管理](./sdk-docs/QuotaApi.md)
- [UsageApi — 使用量统计](./sdk-docs/UsageApi.md)

#### 上传 / 下载

- [Uploader — 文件上传](./sdk-docs/Uploader.md)
- [Downloader — 文件下载](./sdk-docs/Downloader.md)

### 其他文档

- [API 文档](https://cloud.tencent.com/document/product/1339)

## 许可证

本项目采用 ISC 许可证。

## 相关链接

- [腾讯云智能媒资托管](https://cloud.tencent.com/product/smh)
- [产品文档](https://cloud.tencent.com/document/product/1339)
- [控制台](https://console.cloud.tencent.com/smh)

## 支持

如有问题或建议，欢迎：

- 提交 Issue
- 查看官方文档
- 联系腾讯云技术支持
