ProcessTransport.js 3.12 KB
/**
 * ProcessTransport - 子进程传输层实现
 *
 * 通过 child_process.spawn 创建子进程,使用 stdio 管道进行通信。
 * 支持 NDJSON (Newline Delimited JSON) 格式的消息。
 */

const { spawn } = require('child_process');
const { Transport } = require('./Transport');

/**
 * 子进程传输层
 *
 * 负责:
 * - 启动子进程
 * - 管理 stdio 管道通信
 * - 处理数据缓冲和分包
 * - 解析 NDJSON 消息
 */
class ProcessTransport extends Transport {
  constructor() {
    super();
    this.child = null;
  }

  /**
   * 检查是否已连接
   */
  get isConnected() {
    return this.child !== null && !this.child.killed;
  }

  /**
   * 启动子进程并建立连接
   * @param {string} command - 可执行命令
   * @param {string[]} args - 参数列表
   * @param {object} options - spawn 选项
   */
  async connect(command, args, options = {}) {
    return new Promise((resolve, reject) => {
      try {
        console.log(`[ProcessTransport] 启动进程: ${command} ${args.join(' ')}`);

        // 打印关键环境变量
        if (options.env) {
          const criticalVars = ['ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_BASE_URL'];
          console.log('[ProcessTransport] 子进程环境变量:');
          criticalVars.forEach(key => {
            if (options.env[key]) {
              const masked = key.includes('TOKEN')
                ? options.env[key].substring(0, 12) + '...'
                : options.env[key];
              console.log(`  ✅ ${key}: ${masked}`);
            } else {
              console.log(`  ❌ ${key}: 未设置`);
            }
          });
        } else {
          console.log('[ProcessTransport] ⚠️  未传递 env 参数,子进程将继承父进程环境');
        }

        this.child = spawn(command, args, {
          stdio: ['pipe', 'pipe', 'pipe'],
          ...options,
        });

        // 监听进程错误
        this.child.on('error', (err) => {
          this.emit('error', err);
          reject(err);
        });

        // 监听进程退出
        this.child.on('exit', (code, signal) => {
          this.emit('close', code, signal);
        });

        // 监听标准错误
        this.child.stderr?.on('data', (data) => {
          this.emit('stderr', data.toString());
        });

        // 监听标准输出 — 使用基类的 _handleData
        this.child.stdout?.on('data', (data) => {
          this._handleData(data);
        });

        // 等待进程启动
        setTimeout(() => {
          if (this.isConnected) {
            resolve();
          } else {
            resolve();
          }
        }, 500);

      } catch (err) {
        reject(err);
      }
    });
  }

  /**
   * 发送消息
   * @param {object} msg - 消息对象
   */
  send(msg) {
    if (!this.isConnected || this.child.stdin.destroyed) {
      throw new Error('Transport is closed');
    }
    const str = JSON.stringify(msg);
    this.child.stdin.write(str + '\n');
  }

  /**
   * 关闭连接
   */
  close() {
    if (this.child) {
      this.child.kill();
      this.child = null;
    }
    this.buffer = '';
  }
}

module.exports = { ProcessTransport };