tirtc-devtools-cli 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # TiRTC DevTool CLI
1
+ # TiRTC DevTools CLI
2
2
 
3
- `devtools/cli/` 承接 `TiRTC DevTool CLI`。它是当前唯一用户界面的产品门面 (command client),为用户提供了一套操作 `Matrix Host` 控制面的终端工具。
3
+ `devtools/cli/` 承接 `TiRTC DevTools CLI`。它是当前唯一用户界面的产品门面 (command client),为用户提供了一套操作 `Matrix Host` 控制面的终端工具。
4
4
 
5
5
  ## 使用说明 (C-Lite)
6
6
 
package/USAGE.md CHANGED
@@ -1,4 +1,4 @@
1
- # TiRTC DevTool CLI(用户手册)
1
+ # TiRTC DevTools CLI(用户手册)
2
2
 
3
3
  `tirtc-devtools-cli` 是基于 `app-server/host` 的命令行门面,用来控制会话、服务、连接、流、命令通道与调试工具。
4
4
 
@@ -126,19 +126,23 @@ node devtools/cli/bin/tirtc-devtools-cli.js --help
126
126
  - `--service-entry <entry>`:附加到组合 payload,便于扫码后直接连到目标服务。
127
127
  - `--user-ttl-seconds <seconds>`:覆盖 user token ttl。
128
128
  - `--channel-ttl-seconds <seconds>`:覆盖 channel token ttl。
129
+ - `--qr-error-correction-level <level>`:设置二维码纠错级别,可选 `L/M/Q/H`,默认 `M`。
130
+ - `--ascii-max-columns <columns>`:显式限制 ASCII 二维码最大宽度;不传时优先读取当前终端宽度或 `COLUMNS`。
129
131
 
130
132
  默认输出四部分:
131
133
 
132
134
  - `Issued Token Summary`:面向人类阅读的摘要,快速确认 local/peer/entry/ttl。
133
135
  - `Token`:单独罗列 token 字符串,便于复制粘贴。
134
136
  - `Payload JSON`:组合 JSON,对方或 Agent 可直接复制使用。
137
+ - `QR Code ASCII`:非 `--json` 模式下尽量输出 ASCII 二维码;如果终端宽度不足,会提示改看 PNG。
135
138
  - `QR Code PNG`:基于组合 JSON 生成的本地 PNG 文件绝对路径。适合人工打开图片后扫码。
136
139
 
137
140
  说明:
138
141
 
139
142
  - 组合 JSON 的 `type` 固定为 `tirtc-connect-token`。
140
143
  - PNG 二维码承载的是组合 JSON,不再暴露 `secret_key`。
141
- - `--json` 模式下会输出 `payload`、`payloadJson`、`token` 和 `qrCodePngPath`。
144
+ - `--json` 模式下只输出 `payload`、`payloadJson`、`token` 和 `qrCodePngPath`,不输出 ASCII 二维码。
145
+ - 如果觉得 ASCII 二维码太密,可把纠错级别从默认 `M` 调低到 `L`;如果希望更耐遮挡,可调高到 `Q` 或 `H`,但二维码会更密。
142
146
 
143
147
  ### Stream
144
148
 
@@ -9,7 +9,7 @@ const session_manager_1 = require("./session_manager");
9
9
  const media_assets_1 = require("./media_assets");
10
10
  const transport_1 = require("./transport");
11
11
  const token_tool_1 = require("./token_tool");
12
- const CLI_VERSION = '0.0.1';
12
+ const CLI_VERSION = '0.0.4';
13
13
  const HOST_VERSION = '1.0.0';
14
14
  const PROTOCOL_VERSION = '1.0.0';
15
15
  if (process.argv.includes('--version') || process.argv.includes('-V')) {
@@ -551,6 +551,8 @@ token.command('issue <access_id> <secret_key> <local_id> <peer_id>')
551
551
  .option('--service-entry <entry>', '可选 service entry;用于组合连接 payload 与二维码 PNG')
552
552
  .option('--user-ttl-seconds <seconds>', '可选 user token ttl(秒)')
553
553
  .option('--channel-ttl-seconds <seconds>', '可选 channel token ttl(秒)')
554
+ .option('--qr-error-correction-level <level>', '二维码纠错级别:L/M/Q/H;默认 M')
555
+ .option('--ascii-max-columns <columns>', 'ASCII 二维码最大宽度;不传时优先读取当前终端宽度或 COLUMNS')
554
556
  .action((access_id, secret_key, local_id, peer_id, commandOptions) => {
555
557
  const parsePositiveInt = (name, raw) => {
556
558
  if (raw === undefined) {
@@ -562,6 +564,16 @@ token.command('issue <access_id> <secret_key> <local_id> <peer_id>')
562
564
  }
563
565
  return parsed;
564
566
  };
567
+ const parseQrErrorCorrectionLevel = (raw) => {
568
+ if (raw === undefined) {
569
+ return undefined;
570
+ }
571
+ const normalized = raw.trim().toUpperCase();
572
+ if (normalized !== 'L' && normalized !== 'M' && normalized !== 'Q' && normalized !== 'H') {
573
+ throw new Error('qr-error-correction-level must be one of: L, M, Q, H');
574
+ }
575
+ return normalized;
576
+ };
565
577
  runAndExit(runTokenIssue({
566
578
  accessId: access_id,
567
579
  secretKey: secret_key,
@@ -571,6 +583,8 @@ token.command('issue <access_id> <secret_key> <local_id> <peer_id>')
571
583
  serviceEntry: commandOptions.serviceEntry,
572
584
  userTtlSeconds: parsePositiveInt('user-ttl-seconds', commandOptions.userTtlSeconds),
573
585
  channelTtlSeconds: parsePositiveInt('channel-ttl-seconds', commandOptions.channelTtlSeconds),
586
+ qrErrorCorrectionLevel: parseQrErrorCorrectionLevel(commandOptions.qrErrorCorrectionLevel),
587
+ asciiMaxColumns: parsePositiveInt('ascii-max-columns', commandOptions.asciiMaxColumns),
574
588
  }, getCliOptions()));
575
589
  });
576
590
  const stream = program.command('stream').description('流控制:发送/接收/请求策略');
@@ -7,6 +7,8 @@ export type TokenIssueInput = {
7
7
  serviceEntry?: string;
8
8
  userTtlSeconds?: number;
9
9
  channelTtlSeconds?: number;
10
+ qrErrorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H';
11
+ asciiMaxColumns?: number;
10
12
  };
11
13
  export type IssuedTokenPayload = {
12
14
  version: 1;
@@ -25,9 +27,12 @@ export type TokenIssueOutput = {
25
27
  payloadJson: string;
26
28
  token: string;
27
29
  qrCodePngPath: string;
30
+ qrCodeAscii: string;
31
+ qrCodeAsciiIncluded: boolean;
28
32
  };
29
33
  export declare function issueToken(input: TokenIssueInput): Promise<string>;
30
34
  export declare function buildIssuedTokenPayload(input: TokenIssueInput, token: string): IssuedTokenPayload;
31
- export declare function writePngQrcode(payloadJson: string, outputPath: string): Promise<string>;
35
+ export declare function writePngQrcode(payloadJson: string, outputPath: string, errorCorrectionLevel: 'L' | 'M' | 'Q' | 'H'): Promise<string>;
36
+ export declare function buildAsciiQrcode(payloadJson: string, terminalColumns?: number, errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H'): Promise<string>;
32
37
  export declare function issueTokenWithQrcode(input: TokenIssueInput): Promise<TokenIssueOutput>;
33
38
  export declare function formatTokenIssueConsoleOutput(output: TokenIssueOutput): string;
@@ -6,17 +6,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.issueToken = issueToken;
7
7
  exports.buildIssuedTokenPayload = buildIssuedTokenPayload;
8
8
  exports.writePngQrcode = writePngQrcode;
9
+ exports.buildAsciiQrcode = buildAsciiQrcode;
9
10
  exports.issueTokenWithQrcode = issueTokenWithQrcode;
10
11
  exports.formatTokenIssueConsoleOutput = formatTokenIssueConsoleOutput;
11
- const module_1 = require("module");
12
+ const child_process_1 = require("child_process");
12
13
  const fs_1 = __importDefault(require("fs"));
13
14
  const path_1 = __importDefault(require("path"));
14
15
  const qrcode_1 = __importDefault(require("qrcode"));
15
16
  const embedded_paths_1 = require("./embedded_paths");
16
- const runtimeRequire = (0, module_1.createRequire)(__filename);
17
17
  const kDefaultOpenapiEntry = 'http://api-test-tirtc.tange365.com';
18
18
  const kDefaultUserTtlSeconds = 86400;
19
19
  const kDefaultChannelTtlSeconds = 300;
20
+ const kAsciiQrQuietZoneModules = 2;
20
21
  function pathExists(filePath) {
21
22
  return fs_1.default.existsSync(filePath);
22
23
  }
@@ -117,18 +118,6 @@ function resolveAddonPath(repoRoot, runtimePlatform) {
117
118
  throw new Error('credential addon missing for platform ' + runtimePlatform +
118
119
  '; run: npm --prefix app-server run build:native');
119
120
  }
120
- function loadCredentialBinding() {
121
- const runtimePlatform = resolveRuntimePlatform();
122
- const runtimeBundleRoot = resolveRuntimeBundleRoot(runtimePlatform);
123
- const repoRoot = resolveRepoRoot();
124
- ensureRuntimeBundleReady(runtimeBundleRoot);
125
- const addonPath = resolveAddonPath(repoRoot, runtimePlatform);
126
- const loaded = runtimeRequire(addonPath);
127
- if (!loaded || typeof loaded.issueToken !== 'function') {
128
- throw new Error('credential addon missing issueToken() export: ' + addonPath);
129
- }
130
- return loaded;
131
- }
132
121
  function normalizeIssuedToken(token) {
133
122
  const normalized = token.trim();
134
123
  if (normalized.length === 0) {
@@ -136,25 +125,120 @@ function normalizeIssuedToken(token) {
136
125
  }
137
126
  return normalized;
138
127
  }
128
+ function buildIssueTokenHelperSource() {
129
+ return [
130
+ 'const addon = require(process.env.TIRTC_CREDENTIAL_ADDON_PATH_HELPER);',
131
+ 'const input = JSON.parse(process.env.TIRTC_CREDENTIAL_INPUT_JSON || "{}");',
132
+ 'const originalWrite = process.stderr.write.bind(process.stderr);',
133
+ 'process.stderr.write = ((chunk, encoding, callback) => {',
134
+ ' if (typeof callback === "function") {',
135
+ ' callback();',
136
+ ' }',
137
+ ' return true;',
138
+ '});',
139
+ 'try {',
140
+ ' const token = addon.issueToken({',
141
+ ' openapiEntry: input.openapiEntry,',
142
+ ' accessId: input.accessId,',
143
+ ' secretKey: input.secretKey,',
144
+ ' localId: input.localId,',
145
+ ' peerId: input.peerId,',
146
+ ' userTtlSeconds: input.userTtlSeconds,',
147
+ ' channelTtlSeconds: input.channelTtlSeconds,',
148
+ ' });',
149
+ ' process.stdout.write(String(token).trim());',
150
+ '} finally {',
151
+ ' process.stderr.write = originalWrite;',
152
+ '}',
153
+ ].join('\n');
154
+ }
155
+ function issueTokenViaSubprocess(addonPath, input) {
156
+ const result = (0, child_process_1.spawnSync)(process.execPath, ['-e', buildIssueTokenHelperSource()], {
157
+ encoding: 'utf8',
158
+ env: {
159
+ ...process.env,
160
+ TIRTC_CREDENTIAL_ADDON_PATH_HELPER: addonPath,
161
+ TIRTC_CREDENTIAL_INPUT_JSON: JSON.stringify(input),
162
+ },
163
+ stdio: ['ignore', 'pipe', 'pipe'],
164
+ });
165
+ if (result.error) {
166
+ throw result.error;
167
+ }
168
+ if (result.status !== 0) {
169
+ const details = String(result.stderr || result.stdout || 'issue token subprocess failed').trim();
170
+ throw new Error(details || 'issue token subprocess failed');
171
+ }
172
+ return normalizeIssuedToken(String(result.stdout || ''));
173
+ }
174
+ function readQrModule(modules, row, column) {
175
+ return Boolean(modules.data[row * modules.size + column]);
176
+ }
177
+ function resolveTerminalColumns() {
178
+ if (typeof process.stdout.columns === 'number' && Number.isInteger(process.stdout.columns) && process.stdout.columns > 0) {
179
+ return process.stdout.columns;
180
+ }
181
+ const raw = process.env.COLUMNS;
182
+ if (!raw) {
183
+ return undefined;
184
+ }
185
+ const parsed = Number.parseInt(raw, 10);
186
+ if (!Number.isInteger(parsed) || parsed <= 0) {
187
+ return undefined;
188
+ }
189
+ return parsed;
190
+ }
191
+ function resolveAsciiMaxColumns(explicit) {
192
+ if (explicit !== undefined) {
193
+ return explicit;
194
+ }
195
+ return resolveTerminalColumns();
196
+ }
197
+ function readQrModuleOrWhite(modules, row, column) {
198
+ if (row < 0 || column < 0 || row >= modules.size || column >= modules.size) {
199
+ return false;
200
+ }
201
+ return readQrModule(modules, row, column);
202
+ }
203
+ function renderHalfBlockQr(modules) {
204
+ const lines = [];
205
+ const start = -kAsciiQrQuietZoneModules;
206
+ const end = modules.size + kAsciiQrQuietZoneModules;
207
+ for (let row = start; row < end; row += 2) {
208
+ let line = '';
209
+ for (let column = start; column < end; column += 1) {
210
+ const top = readQrModuleOrWhite(modules, row, column);
211
+ const bottom = readQrModuleOrWhite(modules, row + 1, column);
212
+ if (top && bottom) {
213
+ line += '█';
214
+ }
215
+ else if (top) {
216
+ line += '▀';
217
+ }
218
+ else if (bottom) {
219
+ line += '▄';
220
+ }
221
+ else {
222
+ line += ' ';
223
+ }
224
+ }
225
+ lines.push(line);
226
+ }
227
+ return lines.join('\n');
228
+ }
139
229
  async function issueToken(input) {
140
- const binding = loadCredentialBinding();
141
- let token;
230
+ const runtimePlatform = resolveRuntimePlatform();
231
+ const runtimeBundleRoot = resolveRuntimeBundleRoot(runtimePlatform);
232
+ const repoRoot = resolveRepoRoot();
233
+ ensureRuntimeBundleReady(runtimeBundleRoot);
234
+ const addonPath = resolveAddonPath(repoRoot, runtimePlatform);
142
235
  try {
143
- token = binding.issueToken({
144
- openapiEntry: input.openapiEntry,
145
- accessId: input.accessId,
146
- secretKey: input.secretKey,
147
- localId: input.localId,
148
- peerId: input.peerId,
149
- userTtlSeconds: input.userTtlSeconds,
150
- channelTtlSeconds: input.channelTtlSeconds,
151
- });
236
+ return issueTokenViaSubprocess(addonPath, input);
152
237
  }
153
238
  catch (error) {
154
239
  const message = error instanceof Error ? error.message : String(error);
155
240
  throw new Error('issue token failed: ' + message);
156
241
  }
157
- return normalizeIssuedToken(token);
158
242
  }
159
243
  function buildIssuedTokenPayload(input, token) {
160
244
  return {
@@ -170,27 +254,45 @@ function buildIssuedTokenPayload(input, token) {
170
254
  generated_at: new Date().toISOString(),
171
255
  };
172
256
  }
173
- async function writePngQrcode(payloadJson, outputPath) {
257
+ async function writePngQrcode(payloadJson, outputPath, errorCorrectionLevel) {
174
258
  const resolvedPath = path_1.default.resolve(outputPath);
175
259
  ensureDirectory(path_1.default.dirname(resolvedPath));
176
260
  await qrcode_1.default.toFile(resolvedPath, payloadJson, {
177
- errorCorrectionLevel: 'M',
261
+ errorCorrectionLevel,
178
262
  margin: 2,
179
263
  type: 'png',
180
264
  width: 960,
181
265
  });
182
266
  return resolvedPath;
183
267
  }
268
+ async function buildAsciiQrcode(payloadJson, terminalColumns, errorCorrectionLevel = 'M') {
269
+ const qr = qrcode_1.default.create(payloadJson, { errorCorrectionLevel });
270
+ const requiredColumns = qr.modules.size + (kAsciiQrQuietZoneModules * 2);
271
+ const availableColumns = resolveAsciiMaxColumns(terminalColumns);
272
+ if (availableColumns !== undefined && availableColumns < requiredColumns) {
273
+ return [
274
+ '(omitted: terminal width too narrow for ASCII QR)',
275
+ `required_columns: ${requiredColumns}`,
276
+ `available_columns: ${availableColumns}`,
277
+ 'open the QR Code PNG path below instead.',
278
+ ].join('\n');
279
+ }
280
+ return renderHalfBlockQr(qr.modules);
281
+ }
184
282
  async function issueTokenWithQrcode(input) {
185
283
  const token = await issueToken(input);
186
284
  const payload = buildIssuedTokenPayload(input, token);
187
285
  const payloadJson = JSON.stringify(payload);
188
- const qrCodePngPath = await writePngQrcode(payloadJson, buildQrCodePngPath(payload));
286
+ const qrErrorCorrectionLevel = input.qrErrorCorrectionLevel ?? 'M';
287
+ const qrCodePngPath = await writePngQrcode(payloadJson, buildQrCodePngPath(payload), qrErrorCorrectionLevel);
288
+ const qrCodeAscii = await buildAsciiQrcode(payloadJson, input.asciiMaxColumns, qrErrorCorrectionLevel);
189
289
  return {
190
290
  payload,
191
291
  payloadJson,
192
292
  token,
193
293
  qrCodePngPath,
294
+ qrCodeAscii,
295
+ qrCodeAsciiIncluded: !qrCodeAscii.startsWith('(omitted:'),
194
296
  };
195
297
  }
196
298
  function formatTokenIssueConsoleOutput(output) {
@@ -210,6 +312,9 @@ function formatTokenIssueConsoleOutput(output) {
210
312
  'Payload JSON:',
211
313
  JSON.stringify(output.payload, null, 2),
212
314
  '',
315
+ 'QR Code ASCII:',
316
+ output.qrCodeAscii,
317
+ '',
213
318
  'QR Code PNG:',
214
319
  output.qrCodePngPath,
215
320
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tirtc-devtools-cli",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -209,8 +209,7 @@ TirtcError tirtc_conn_attach_audio_input(TirtcConn* connection, uint8_t stream_i
209
209
  * ::tirtc_audio_output_set_aout and any desired options before attaching it.
210
210
  *
211
211
  * If an observer is already installed, a successful attach typically reports an
212
- * initial IDLE state before playback begins. A successful attach also asks the
213
- * remote peer to start sending audio for this ::stream_id on this connection.
212
+ * initial IDLE state before playback begins.
214
213
  *
215
214
  * @param connection Target connection.
216
215
  * @param stream_id Remote audio stream identifier to render.
@@ -235,8 +234,7 @@ TirtcError tirtc_conn_detach_audio_input(TirtcConn* connection, uint8_t stream_i
235
234
  /**
236
235
  * @brief Detach an audio output from a connection stream.
237
236
  *
238
- * This call is idempotent. A successful detach also asks the remote peer to
239
- * stop sending audio for this ::stream_id on this connection.
237
+ * This call is idempotent.
240
238
  *
241
239
  * @param connection Target connection.
242
240
  * @param stream_id Stream identifier to detach.
@@ -239,6 +239,8 @@ TirtcError tirtc_conn_attach_video_input(TirtcConn* connection, uint8_t stream_i
239
239
  *
240
240
  * If an observer is already installed, a successful attach typically reports an
241
241
  * initial IDLE state before decoded frames begin to render.
242
+ * Attach is a local bind only; it does not send a remote request for this
243
+ * stream.
242
244
  *
243
245
  * @param connection Target connection.
244
246
  * @param stream_id Remote video stream identifier to render.
@@ -79,6 +79,27 @@ typedef struct TirtcOptions {
79
79
  int environment;
80
80
  } TirtcOptions;
81
81
 
82
+ /**
83
+ * @brief Default media send policy applied by the facade.
84
+ *
85
+ * The effective send gate still uses `connection + stream_id + media_kind` as
86
+ * the smallest scope. The policy only decides how those gates become open.
87
+ */
88
+ typedef enum TirtcMediaSendPolicy {
89
+ /**
90
+ * @brief Keep send gates closed until the consumer explicitly opens them.
91
+ */
92
+ TIRTC_MEDIA_SEND_POLICY_ON_REQUEST = 0,
93
+
94
+ /**
95
+ * @brief Open send gates automatically once the accepted connection exists.
96
+ *
97
+ * When the bound input is ready, the facade may start sending without first
98
+ * waiting for an explicit remote request.
99
+ */
100
+ TIRTC_MEDIA_SEND_POLICY_AUTO_ON_CONNECTED = 1,
101
+ } TirtcMediaSendPolicy;
102
+
82
103
  /**
83
104
  * @brief Options for starting a service-side connection listener.
84
105
  *
@@ -102,6 +123,15 @@ typedef struct TirtcConnServiceStartOptions {
102
123
  * This field is required and must be non-zero.
103
124
  */
104
125
  uint32_t max_connections;
126
+
127
+ /**
128
+ * @brief Service-level media send policy applied to accepted connections.
129
+ *
130
+ * This policy controls how the facade opens per-connection send gates after
131
+ * a connection becomes available. It does not change the underlying
132
+ * transport request/release primitives.
133
+ */
134
+ TirtcMediaSendPolicy media_send_policy;
105
135
  } TirtcConnServiceStartOptions;
106
136
 
107
137
  /**
@@ -199,9 +229,21 @@ typedef struct TirtcStreamMessage {
199
229
 
200
230
  /**
201
231
  * @brief Fire-and-forget command payload.
232
+ *
233
+ * Command identifiers are application-defined and must be agreed with the
234
+ * remote peer.
235
+ *
236
+ * The current runtime accepts application command ids in the range
237
+ * `[0x1000, 0x7FFF]`. Values below `0x1000` are reserved for
238
+ * runtime-internal commands and must not be used by consumers.
202
239
  */
203
240
  typedef struct TirtcConnCommand {
204
- /** @brief Application-defined command identifier. */
241
+ /**
242
+ * @brief Application-defined command identifier agreed with the remote peer.
243
+ *
244
+ * A request and its reply typically reuse the same ::command_id, while the
245
+ * per-request correlation is carried separately by ::remote_request_id.
246
+ */
205
247
  uint16_t command_id;
206
248
 
207
249
  /** @brief Payload bytes, or NULL when ::length is zero. */
@@ -213,9 +255,21 @@ typedef struct TirtcConnCommand {
213
255
 
214
256
  /**
215
257
  * @brief Request payload for an asynchronous command round-trip.
258
+ *
259
+ * Command identifiers are application-defined and must be agreed with the
260
+ * remote peer.
261
+ *
262
+ * The current runtime accepts application command ids in the range
263
+ * `[0x1000, 0x7FFF]`. Values below `0x1000` are reserved for
264
+ * runtime-internal commands and must not be used by consumers.
216
265
  */
217
266
  typedef struct TirtcConnCommandRequest {
218
- /** @brief Application-defined command identifier. */
267
+ /**
268
+ * @brief Application-defined command identifier agreed with the remote peer.
269
+ *
270
+ * In the common request-response pattern, the responder echoes the same
271
+ * ::command_id back in ::TirtcConnCommandResponse.
272
+ */
219
273
  uint16_t command_id;
220
274
 
221
275
  /** @brief Payload bytes, or NULL when ::length is zero. */
@@ -235,9 +289,20 @@ typedef struct TirtcConnCommandRequest {
235
289
 
236
290
  /**
237
291
  * @brief Response payload returned by a remote peer or local reply logic.
292
+ *
293
+ * Command identifiers are application-defined and must be agreed with the
294
+ * remote peer.
295
+ *
296
+ * The current runtime accepts application command ids in the range
297
+ * `[0x1000, 0x7FFF]`. Values below `0x1000` are reserved for
298
+ * runtime-internal commands and must not be used by consumers.
238
299
  */
239
300
  typedef struct TirtcConnCommandResponse {
240
- /** @brief Application-defined command identifier. */
301
+ /**
302
+ * @brief Application-defined command identifier agreed with the remote peer.
303
+ *
304
+ * This usually matches the original request's ::command_id.
305
+ */
241
306
  uint16_t command_id;
242
307
 
243
308
  /** @brief Payload bytes, or NULL when ::length is zero. */
@@ -490,6 +555,11 @@ TirtcError tirtc_conn_send_stream_message(TirtcConn* connection, uint8_t stream_
490
555
  /**
491
556
  * @brief Send an application-defined command without waiting for a response.
492
557
  *
558
+ * Choose ::TirtcConnCommand.command_id from the command ids agreed with the
559
+ * remote peer. The current runtime accepts application command ids in
560
+ * `[0x1000, 0x7FFF]`; values below `0x1000` are reserved for
561
+ * runtime-internal commands.
562
+ *
493
563
  * @param connection Target connection.
494
564
  * @param command Non-NULL command payload.
495
565
  * @return ::TIRTC_ERROR_OK on success.
@@ -500,6 +570,11 @@ TirtcError tirtc_conn_send_command(TirtcConn* connection, const TirtcConnCommand
500
570
  * @brief Send an application-defined request command and complete it
501
571
  * asynchronously.
502
572
  *
573
+ * Choose ::TirtcConnCommandRequest.command_id from the command ids agreed
574
+ * with the remote peer. The current runtime accepts application command
575
+ * ids in `[0x1000, 0x7FFF]`; values below `0x1000` are reserved for
576
+ * runtime-internal commands.
577
+ *
503
578
  * Callback delivery is mixed synchronous/asynchronous. If request submission
504
579
  * fails immediately after the facade has accepted the request, ::callback may
505
580
  * run before this function returns. After request submission succeeds,
@@ -521,7 +596,8 @@ TirtcError tirtc_conn_request_command(TirtcConn* connection, const TirtcConnComm
521
596
  /**
522
597
  * @brief Reply to an inbound remote command request.
523
598
  *
524
- * Use the ::remote_request_id delivered by
599
+ * In the common request-response pattern, ::TirtcConnCommandResponse.command_id
600
+ * matches the inbound request's ::command_id. Use the ::remote_request_id delivered by
525
601
  * ::TirtcConnObserver.on_remote_command_request on the same connection. If the
526
602
  * request is no longer pending, this function returns a not-ready error.
527
603
  * In the normal case, reply at most once for each delivered request id.
@@ -1,8 +1,8 @@
1
1
  platform=macos-arm64
2
- staged_at_utc=2026-04-09T05:31:28Z
2
+ staged_at_utc=2026-04-09T05:56:18Z
3
3
  source_sdk=/Users/allenfeng/Development/Repositories/tirtc-nexus/tirtc-matrix/.build/sdk/macos-arm64
4
4
 
5
- 58cbe872af5a68a1f5832912ab9deb7ef5af1ac5d19439b523ce6619978214c0 include/tirtc/audio.h
5
+ dcca0b7ab8055d84e5bcfab7f1dde56df9037c40ddb99e9ca924ef2e867d8309 include/tirtc/audio.h
6
6
  6d972ccfe150a3b4f2d7f18fa92b3ade9210c1c8bb754d061ac6b7997b59e2cb include/tirtc/audio_codec.h
7
7
  7bacbdb2d8bb10d6444036a8fef42f2a8e3ea34dfc38e165ee678d61f189db41 include/tirtc/audio_frame.h
8
8
  84f1bbe67efa15ab3b2d995d661025aac43e2fe547a14a012a24d89cfe3cb859 include/tirtc/audio_io.h
@@ -12,7 +12,7 @@ c2e1f31dcc75be461c577d18b1cebe32774f212d51cb4dd2a5b5a9bfe62b693e include/tirtc/
12
12
  51cbc911fe9f9834046f0e0a1a7cdd814a8e194a615894a8b4d11f9e5f095610 include/tirtc/audio_io_windows.h
13
13
  21f60729117260a44af22c1af986ef17d22673b102b7b7a035f492d0665cce16 include/tirtc/audio_processing.h
14
14
  0ca7c3c630b1242f51a0fd8154097c0a332b4c816a5707090e4381719852998c include/tirtc/audio_sample_rate.h
15
- 64d0005d726ed7d29e6c5a9c7bc25e931693ba8fa4ab6af8d21e7adc8767f66e include/tirtc/av.h
15
+ 59b7df33fdc2ad92b3142299ffab832443179ef895784ad51a2f6544c5b02766 include/tirtc/av.h
16
16
  4198c95c48ae579d1c782635b00fa5c9009d0c56ba466e6e3873e8298faae029 include/tirtc/credential.h
17
17
  92da1614c1cbc22d5ba7c1aefd3bbfba88be4a698d1b9990f470fe959ddd84ba include/tirtc/error.h
18
18
  ae805545a9515edc9b94262e72ad2c7b7d649288166f4daeb450d8a55e82ae0b include/tirtc/foundation/build_info.h
@@ -25,7 +25,7 @@ ae805545a9515edc9b94262e72ad2c7b7d649288166f4daeb450d8a55e82ae0b include/tirtc/
25
25
  a4f8ab44c1a20ad37f250363a403a9d4d007e61ddb2426c7966dbc05f6a04b4f include/tirtc/media_live_source.h
26
26
  1b3be6954e547f91a047866438bff1820c8406afaf91cef68ddee29a6ac70234 include/tirtc/media_uplink.h
27
27
  1e12daf9c5fc2e2f848444688081c9be0cb738245f0155327e8003b49308058f include/tirtc/transport.h
28
- f836768a4dd8b8595bd53cd96cca98bb56e802dd3ee3e520b18546d999908928 include/tirtc/trp.h
28
+ 99b4b31b8359009ee74f0f2a52be6473d9c6c37b184e53eeb554d4bf61f6ee33 include/tirtc/trp.h
29
29
  dff5b0a0ac4a40ad17c93e1e56b3c416371c81ab365e287ea8cd6ce37ccbed3b include/tirtc/video_codec.h
30
30
  e51379666c199588cc33279ccf52248035d1cae3d1d468b1615ebf29f0b39c9c include/tirtc/video_frame.h
31
31
  d920afad955b9f206b02b19ca152315190fa84ab6f24e895a5b24c3ab9ffd701 include/tirtc/video_io.h
@@ -36,14 +36,14 @@ cae0bbeb884e5466a56da15182c78cc22baab6c743f349a58d3595f623333585 include/tirtc/
36
36
  8cd6b66bea14890a665cc317f8572429b2c3e4463773f8b77c1e4dc30a4a8747 include/tirtc/video_processing.h
37
37
  b39daee6a3d39bf0ca20c45084601133c4198de8dca848dcff6dd9c70ae99016 lib/libcrypto.a
38
38
  c052857ef315e3d61db9c862cad10709a3a6b2487dc41799cbe4d74a805de875 lib/libcrypto.dylib
39
- e96e6d57ff90f3cb34d98d69036987c4d4839c48094168261fbe776a524185a1 lib/libmatrix_runtime_audio.a
40
- 8255fc3eb34a44c5ee5cd6f1ca88d06392527fbe91d492692152eb719fda9887 lib/libmatrix_runtime_credential.a
41
- 467c2440813c73211dd71775d7a49fdeb785975c998ab1c7268e967c07650399 lib/libmatrix_runtime_facade.a
42
- 9499a182dde5cd3cd908ddd2d44b1d6b2cd884bd5041379f2d1144cbb49297b3 lib/libmatrix_runtime_foundation_http.a
43
- 79aba2a4f43ca5f4aca9187d07c656b6696e9b644bf678418ed2e79ed718ce2e lib/libmatrix_runtime_foundation_logging.a
44
- 99ae6ebbe3f3faa3b9591e049691842cef8bdbc3fd6f1e0cb82c7f9acb7ac85e lib/libmatrix_runtime_media.a
45
- a9cba88ca9db63c2712b3e0088c8525e55763ce62836b238b042bb0514b0f669 lib/libmatrix_runtime_transport.a
46
- 86845113a79fa1c7503b17bcf44cff061b5491127d38995206c2afce59aa4560 lib/libmatrix_runtime_video.a
39
+ 6ad596f3ad055be11f121f418aeff4e02c650f3cec6b37c471ed54e9498a1268 lib/libmatrix_runtime_audio.a
40
+ 42d1a2e4aa59d53c0c35ad204a365b7d7d83698c21857f5ffe7421da240da3e1 lib/libmatrix_runtime_credential.a
41
+ 2153ac1352ebb401f7920e8358c4988f9bd7f5275009ab94f6c4b7d7eb2a71c3 lib/libmatrix_runtime_facade.a
42
+ 25d70d18d4b69e0f28514035811a4ae8e18a1dcbcdd88d6a68e3d7609e2537bb lib/libmatrix_runtime_foundation_http.a
43
+ 78b82c288d2e58f1219d5f56772a1dc8683e8da5f2f4724c160e6bf97f9ddadc lib/libmatrix_runtime_foundation_logging.a
44
+ 548230c283e262838d2492ae28817870cd191eb3b9cb3e943446645562a2e20f lib/libmatrix_runtime_media.a
45
+ e5afb3d24fd62ad5f4fd68ea7ba65957e79bf27d5669a8d6bceb44aa9f297c16 lib/libmatrix_runtime_transport.a
46
+ e0a087c9d0b0c929a20677db1aa8e053813baa3682458f407f04dc751d0d225a lib/libmatrix_runtime_video.a
47
47
  c11c65d373a127028350c41fa58cd2d1223f2b5d70a84e13b115d90daaba25ca lib/libssl.a
48
48
  ef1c1104bbdd2528ed7b958fb7252bd6249875f92300b0c9577d6c4bd6c0d88a lib/libssl.dylib
49
49
  dc3e0e6ded26e90fd59c166933a5289d57f4cf25fbe6246b6b83c86fe898e63d lib/libwebrtc_apm.a