# @3cx/call-control-sdk 中文文档

规范地址：https://npmcn.com/package/%403cx/call-control-sdk/

版本：0\.1\.10
npm 元数据更新时间：2026-08-04T06:58:32.327Z
中文译文更新时间：2026-11-07T08:12:22.568Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%403cx/call-control-sdk
原文对照：https://npmcn.com/package/%403cx/call-control-sdk/?lang=original

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：97.1 kB，33 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=24\.0\.0
- 运行依赖：2 个
- peer 依赖：@types/node \>=24
- 首次发布：2026-07-29T09:06:09.131Z
- 最新版本发布：2026-08-04T06:58:32.134Z
- 近 30 天下载量：1992（2026-10-05 至 2026-11-03，npm 官方下载统计）
- 已发布版本数：4（近 12 个月 4 个）

## 中文简介

与 3CX 调用控制 API 交互的 SDK

## 安装

```sh
npm install @3cx/call-control-sdk
```

也可以使用：`pnpm add @3cx/call-control-sdk`、`yarn add @3cx/call-control-sdk`、`bun add @3cx/call-control-sdk`

## README

# @ 3cx/ 呼叫控制- sdk

TypeScript的3CX呼叫控制SDK为3CX呼叫控制API提供了更高级的接口.

## 安装

```bash
npm install @3cx/call-control-sdk
```

## 快速启动

```typescript
import { CallControlClient } from "@3cx/call-control-sdk";

const client = new CallControlClient({
  pbxBase: "https://your-pbx.example.com",
  appId: "your-app-id",
  appSecret: "your-app-secret",
});

await client.connect();

client.on("participantConnected", async (participant) => {
  console.log(`Participant ${participant.id} connected`);

  // Get audio stream from the caller (PCM 8kHz 16-bit mono)
  const audioStream = await participant.getAudioStream();

  // Create audio writer to the caller
  const audioWriter = participant.getAudioWriter();

  // Pipe audio through your AI provider (STT -> Agent -> TTS)
  // ...

  // Write PCM 8kHz 16-bit mono audio back
  audioWriter.write(pcmBuffer);

  // Call actions directly on the participant
  await participant.transfer("1001");
});

client.on("participantDisconnected", (participantId) => {
  // SDK automatically cleans up audio streams/writers
  console.log(`Participant ${participantId} disconnected`);
});
```

## WebSocket 重新连接

在出人意料的断开时,SDK会自动与指数回放连接. 默认的 **永不放弃** (单位:千美元)`maxReconnects: Infinity`) (中文(简体) ).

| 选项 | 默认 | 说明 |
| --- | --- | --- |
| `websocket.maxReconnects` | `Infinity` | 放弃前的尝试。 设定一个有限的数字以停止重试 . |
| `websocket.reconnectDelayMs` | `5000` | 第一次重试前的基数延后( 每次尝试都用 Doubles) . |
| `websocket.reconnectBackoffMaxMs` | `120000` | 每回试2分)上盖. |

延迟会增加大约5分 10分 20分 达到上限 抓取失败 重新连接使用相同的回放. 限制重试 :

```typescript
websocket: { maxReconnects: 5 }
```

## 延期参与者监测

当扩展附着在应用程序的服务本位( 受控 DNs) 时, SDK 发布单独的事件 用于他们的呼叫活动。 这样你就可以监视那些电话 扩展时不将其与应用程序自己的可编程DN的调用相混为一谈.

```typescript
// Calls on the app's own DN - full control (audio + call actions)
client.on("participantConnected", async (participant) => {
  console.log(`Own DN call from ${participant.info.party_caller_id}`);
  const audio = await participant.getAudioStream(); // works
  // ...
});

// Calls on monitored extensions - call actions only, no audio
client.on("extensionParticipantConnected", async (participant) => {
  console.log(
    `Extension ${participant.dn} call from ${participant.info.party_caller_id}`,
  );
  // participant.getAudioStream() -> throws Error
  await participant.transfer("1001"); // works
});

client.on("extensionParticipantDisconnected", (participantId) => {
  console.log(`Extension call ${participantId} ended`);
});
```

### 为什么没有关于扩展参与者的音频?

音频流和DTMF仅供应用程序自己的可编程DN(RoutePoint)使用. 分机电话由分机自己的设备(IP电话、软电话、, ——媒体路径在PBX和那个设备之间直接走. 可编程 扩展从不为这些电话坐在媒体的道路上,所以没有音频可以截取 或注射 。 调用控制动作( 调用、 调用、 调用、 路由) 在信号中工作 级别; `answer()` 需要扩展时 `supportsDirectControl` (中文(简体) ).

### 事件周期说明

路由点( app's own DN) 自动回答来电 - 参与者 几乎总是以身份到达 `Connected`这样 `participantConnected` 立即开火.

扩展功能行为不同: 设备上的第一个调用环( S)`Ringing`),且仅限 变成 `Connected` 用户接后. 这个 `extensionParticipantConnected` 事件 只有在扩展部分实际响应呼叫时才开火. 检测进货( 环行) 呼叫扩展名, 请收听 `extensionParticipantUpdated` 检查 `participant.info.status`数字 :

```typescript
client.on("extensionParticipantUpdated", (participant) => {
  if (participant.info.status === "Ringing") {
    console.log(`Extension ${participant.dn} is ringing`);
    if (participant.supportsDirectControl) {
      void participant.answer(); // uaCSTA leg only
    }
  }
});
```

多设备扩展获取 **每个设备一个参与者腿**调用 `answer()` 编辑 带脚的 `supportsDirectControl === true`而不是移动/软手机腿上.

| 能力 | 拥有 DN( RoutePoint) | 扩展DNA |
| --- | --- | --- |
| 调用事件 | `participantConnected` 编号 : `Updated` 编号 : `Disconnected` | `extensionParticipantConnected` 编号 : `Updated` 编号 : `Disconnected` |
| 音频流 | 对 | 没有 |
| 自动取款机 | 对 | 没有 |
| 传送/ 丢弃 | 对 | 对 |
| 变形 | 对 | 对.. **`Ringing` 仅输入** |
| 路径到 | 对 | 对.. **`Ringing` 或者说 `Connected`** |
| 答复 | 对 | 仅与 `supportsDirectControl` (华夏) |
| 附加数据 | 对 | 对 |

`divert()` 重定向一个未回复的呼叫——在 **`Ringing` 入境** 脚步( 失败) `Connected`) (中文(简体) ). `routeTo()` 在参与者留在电话中时增加其他路线: 有效时间 **`Ringing` 或者说 `Connected`**启用 `transfer()` 已确认的盲转 打电话 也不适用于出入境 **`Dialing`** 脚.

## 外出通话

`makeCall` 返回直接从 API 响应中创建的参与者ID,所以您 用不着等WebSocket事件来联系到这个电话:

```typescript
const participantId = await client.makeCall("101");
// or from a specific source DN:
const participantId = await client.makeCall("101", "cctest2");

if (participantId !== undefined) {
  await client.attachPartyData(participantId, { public_ticket: "1234" });
}
```

PBX 可返回 `202 Accepted` (电话已接受,id尚未提供)。 这样的话 `makeCall` 决定: `undefined` - 通过WebSocket事件追踪电话.

## 所附数据

将元数据附加到呼叫中 `attachParticipantData` 编号 : `attachPartyData`。 。 。 . **每把钥匙 必须是前缀 `public_`** - PBX 拒绝带有 HTTP 的其他密钥 `422`。 。 。 .

两种方法都采用 **同一参与方的身份证** 并撰写两个不同的字段 **那个 参与人** 记录(同一条呼叫腿)。 没错 **没有** 瞄准焦距的 DN 或第二个参与者反对:

| SDK API/访问器 | 此参与者的 PBX 字段 | 含义 |
| --- | --- | --- |
| `attachParticipantData` 编号 : `participantData` | `participant_attached_data` | 有关这名参与者自己的腿的数据 |
| `attachPartyData` 编号 : `partyData` | `caller_attached_data` | 此段的会场( 相同的参与者 ID) 中的会场数据 |

两个值都从 **受控参与人手柄** ——相机没有 暴露为单独的对象。 需要时随附缔约方关于监测扩展的数据 元数据以活过传输或类似操作.

```typescript
// via the client (same participantId for both)
await client.attachParticipantData(participantId, { public_ticket: "1234" });
await client.attachPartyData(participantId, { public_peer: "181" });

// or via a participant handle
await participant.attachParticipantData({ public_ticket: "1234" });
await participant.attachPartyData({ public_peer: "181" });

console.log(participant.participantData.public_ticket); // '1234'
console.log(participant.partyData.public_peer); // '181'

await participant.attachPartyData({ public_ticket: "5678" });
await participant.refresh(); // party data is not pushed over WebSocket
console.log(participant.partyData.public_ticket); // '5678'
```

为了检查另一个DNA的腿,使用 `client.getState().callcontrol.get(dn)?.participants` (或从 `getExtensionParticipantHandle`) (中文(简体) ). 没有 `getParticipantByDn` 帮手.

## MCP 一体化

使用带有 3CX MCP 服务器的 SDK 令牌商店 :

```typescript
const authProvider = client.createMcpAuthProvider();
const mcpUrl = client.getMcpUrl();

const mcpServer = new MCPServerStreamableHttp({
  url: mcpUrl,
  authProvider,
});
```

## API 参考

### `Participant`

| 方法 | 说明 |
| --- | --- |
| `id` | 数字参与者编号 |
| `dn` | DN 号码 此参与者属于 |
| `isExtensionParticipant` | `true` 如果这是受监视的扩展参与者 |
| `info` | 原始 3CX `CallParticipant` 元数据 |
| `destroyed` | 是否清理了参与者 |
| `supportsDirectControl` | 密码 `direct_control` (uACSTA); 所需时间 `answer()` 扩展时 |
| `participantData` | 这条腿 `participant_attached_data` 通过 `attachParticipantData()` (单位:千美元)`{}` (无) |
| `partyData` | 剪辑器 `caller_attached_data` 穿过这条腿 `attachPartyData()` (单位:千美元)`{}` (无) |
| `getAudioStream()` | 获取可读 PCM 音频流( 仅拥有 DN) |
| `getAudioWriter()` | 获取可写音频频道( 仅拥有 DN) |
| `makeCall(to)` | 从此参与者的 DN 中设置一个新的独立外出呼叫 |
| `transfer(destination, reason?)` | 转移到目的地 |
| `drop()` | 调用 |
| `answer()` | 回答呼叫( Route Point 或 `supportsDirectControl` 仅限) |
| `routeTo(destination, timeout?, reason?)` | 添加可选择的路由( N)`Ringing` 或者说 `Connected`(中文(简体) ) |
| `divert(destination, reason?)` | 重定向未回复的呼叫( N)`Ringing` (仅输入) |
| `transferToVoiceMail(dn, reason?)` | 转入语音信箱 |
| `routeToVoiceMail(dn, timeout?, reason?)` | 语音邮件路径 |
| `divertToVoiceMail(dn, reason?)` | 切入语音邮件 |
| `attachParticipantData(data)` | 写入 `participant_attached_data` 在这条腿上 |
| `attachPartyData(data)` | 写入 `caller_attached_data` (不是远程DN) |
| `refresh()` | 从 REST API 中获取此腿并更新缓存状态 |
| `cancelStreamQueue()` | 取消 PBX 排队的外出音频( 结束活动 POST; 偏好) `getAudioWriter().clear()` (用于驳船) |

### `CallControlClient`

| 方法 | 说明 |
| --- | --- |
| `connect()` | 连接到 3CX PBX (Auth + WebSocket) |
| `disconnect()` | 断开和清理资源 |
| `getParticipantHandle(id)` | 搜索活动 **自定义** `Participant` 由 ID 处理 |
| `getExtensionParticipantHandle(id)` | 搜索活动 **扩展** `Participant` 用ID处理( 用于监测的DNA) |
| `getAudioStream(participantId)` | 获取可读 PCM 音频流 |
| `createAudioWriter(participantId)` | 创建可写入的音频频道, 并保持保留 |
| `transfer(participantId, destination)` | 转会参与者转会至延期 |
| `drop(participantId)` | 从呼叫中放弃参与者 |
| `answer(participantId)` | 回答呼叫( Route Point 或 `direct_control` / 仅限 uACSTA ) |
| `routeTo(participantId, destination)` | 添加可选择的路由( N)`Ringing` 或者说 `Connected`) (中文(简体) ). 没有 `timeout` ——使用 `participant.routeTo()` 暂停 |
| `divert(participantId, destination)` | 重定向未回复的呼叫( N)`Ringing` (仅输入) |
| `makeCall(to, from?)` | 发送呼叫 返回创建的参与者ID( HTTP 200) 或 `undefined` (HTTP 202) (韩语). `from` 默认为 `appId` |
| `attachParticipantData(participantId, data)` | 写入 `participant_attached_data` 在那个参与者的腿上 |
| `attachPartyData(participantId, data)` | 写入 `caller_attached_data` 该参与者的腿上(相同的身份证;而不是远程DNA) |
| `cancelStreamQueue(participantId)` | 取消 PBX 排队的外出音频( 结束活动 POST; 偏好) `createAudioWriter().clear()` (用于驳船) |
| `controlParticipant(participantId, method, body?)` | 通用呼叫控制动作 |
| `getParticipant(id)` | 原始内容 `CallParticipant` 从状态到 **只有应用程序自己的 DN**。扩展腿:使用 `getExtensionParticipantHandle(id)` 或者说 `getState().callcontrol.get(dn)?.participants` |
| `getState()` | 获取全调控制状态( 所有可见 DN 及其参与者) |
| `getFullInfo()` | 从 REST API 获取完整状态( 不更新缓存) |
| `refreshParticipant(participantId)` | 从 REST API 中获取一个参与者并更新缓存状态 + 控件 |
| `createMcpAuthProvider()` | 创建 MCP 认证提供者 |
| `getMcpUrl()` | 获取 MCP 端点 URL |

### 事件 - 路由点( Own DN)

在应用程序自己的可编程DN上为参与者被炒作. 全能控制:音频流,DTMF,和调用动作.

| 活动 | 有效载荷 | 说明 |
| --- | --- | --- |
| `participantConnected` | `Participant` | 连接到呼叫的参与者 |
| `participantDisconnected` | `number` | 取消召集 |
| `participantUpdated` | `Participant` | 参加国已更改 |
| `dtmf` | `string, number` | DTMF 收到的数字和按键的参与者 |

### 事件 - 有监测的扩展

在服务校长的延期上为与会者解雇。 仅调用控制 - 没有音频或DTMF(媒体路径由扩展自己的设备处理,而不是由可编程扩展处理).

| 活动 | 有效载荷 | 说明 |
| --- | --- | --- |
| `extensionParticipantConnected` | `Participant` | 连接扩展参与者 |
| `extensionParticipantDisconnected` | `number` | 延期参与者已删除 |
| `extensionParticipantUpdated` | `Participant` | 扩展参与者状态已更改 |

### 事件 - 连接

| 活动 | 有效载荷 | 说明 |
| --- | --- | --- |
| `connected` | \- 说吧 | 已连接 WebSocket |
| `disconnected` | \- 说吧 | WebSocket 已断开 |
| `error` | `Error` | 发生错误 |

## 许可证

麻省理工学院

## 常见问题

### @3cx/call-control-sdk 是什么？

与 3CX 调用控制 API 交互的 SDK

### 如何安装 @3cx/call-control-sdk？

在项目目录运行 npm install @3cx/call-control-sdk，或使用 pnpm add / yarn add / bun add。

### @3cx/call-control-sdk 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @3cx/call-control-sdk 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @3cx/call-control-sdk 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=24\.0\.0。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @3cx/call-control-sdk 可以商用吗？

@3cx/call-control-sdk 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @3cx/call-control-sdk 的下载量有多少？

根据 npm 官方下载统计，@3cx/call-control-sdk 在 2026/10/05 – 2026/11/03 这 30 天内被下载 1,992 次，日均约 66 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @3cx/call-control-sdk 还在维护吗？

最新版本 0\.1\.10 发布于 2026/08/04（3 个月前）；至今共发布 4 个版本，其中过去 12 个月发布了 4 个；首次发布于 2026/07/29。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

