factory.js 5.2 KB
/**
 * Connection 工厂 — 组合 Launcher(生命周期)+ Transport(通信)
 *
 * 四维正交架构:
 *   Agent 定义 = 身份 × 消息协议 × 传输方式 × 生命周期
 *
 * 本模块负责后两个维度的组合:
 *   - 根据 launcher config 创建 Launcher 实例(管理 CLI 进程)
 *   - 根据 transport type 创建 Transport 实例(管理通信通道)
 *   - 将两者连接起来,返回 { transport, launcher }
 *
 * Protocol 层只拿到 transport,Launcher 的生命周期由外层管理。
 */

const fs = require('fs');
const path = require('path');

/**
 * 解析 envFile + env 列表,返回 ["KEY=VALUE", ...] 数组
 * 用于 Launcher 的环境变量注入
 */
function resolveEnvVars(launcherConfig, workingDir) {
  const envMap = new Map();

  if (launcherConfig.envFile) {
    const envFilePath = path.resolve(workingDir, launcherConfig.envFile);
    try {
      for (const line of fs.readFileSync(envFilePath, 'utf-8').split('\n')) {
        const trimmed = line.trim();
        if (!trimmed || trimmed.startsWith('#')) continue;
        const eqIdx = trimmed.indexOf('=');
        if (eqIdx > 0) {
          const key = trimmed.substring(0, eqIdx).trim();
          const val = trimmed.substring(eqIdx + 1).trim().replace(/^["']|["']$/g, '');
          envMap.set(key, `${key}=${val}`);
        }
      }
      console.log(`[factory] 加载 envFile: ${envFilePath} (${envMap.size} 项)`);
    } catch (err) {
      console.warn(`[factory] envFile 读取失败: ${envFilePath} — ${err.message}`);
    }
  }

  for (const entry of (launcherConfig.env || [])) {
    if (entry.includes('=')) {
      const key = entry.substring(0, entry.indexOf('='));
      envMap.set(key, entry);
    } else if (process.env[entry]) {
      envMap.set(entry, `${entry}=${process.env[entry]}`);
    }
  }

  return Array.from(envMap.values());
}

/**
 * 创建连接(Launcher + Transport 组合)
 *
 * @param {object} agentDef - agent 配置(新 schema)
 * @param {string} agentDef.protocol - 消息协议名
 * @param {string} agentDef.transport - 传输方式("pipe" | "websocket")
 * @param {string} agentDef.launcher - 引用的 launcher 名称
 * @param {object} [agentDef.transportConfig] - 传输额外配置(如 { url })
 * @param {object} launcherDef - launcher 配置
 * @param {string} workingDir - 工作目录
 * @param {object} [protocolOverrides] - 协议层补充参数(如 CLI args 覆盖)
 * @returns {Promise<{ transport: Transport, launcher: Launcher }>}
 */
async function createConnection(agentDef, launcherDef, workingDir, protocolOverrides = {}) {
  // ─── 1. 创建 Launcher ───
  const launcher = createLauncher(launcherDef);

  // 准备 launcher 启动参数
  const launchOptions = buildLaunchOptions(launcherDef, workingDir, protocolOverrides);
  const handle = await launcher.start(launchOptions);

  // ─── 2. 创建 Transport ───
  const transportType = agentDef.transport || inferTransport(launcherDef);
  const transport = await createTransport(transportType, handle, agentDef.transportConfig);

  return { transport, launcher };
}

/**
 * 根据 launcher 配置推断默认 transport 类型
 */
function inferTransport(launcherDef) {
  if (launcherDef.type === 'process') return 'pipe';
  return 'websocket'; // none 默认走 ws
}

/**
 * 创建 Launcher 实例
 */
function createLauncher(launcherDef) {
  switch (launcherDef.type) {
    case 'process': {
      const { ProcessLauncher } = require('../launcher/ProcessLauncher');
      return new ProcessLauncher();
    }
    case 'none': {
      const { NoneLauncher } = require('../launcher/NoneLauncher');
      return new NoneLauncher();
    }
    default:
      throw new Error(`[factory] 未知 launcher 类型: ${launcherDef.type}`);
  }
}

/**
 * 构建 Launcher.start() 的参数
 */
function buildLaunchOptions(launcherDef, workingDir, protocolOverrides) {
  switch (launcherDef.type) {
    case 'process': {
      // 强制删除 CLAUDECODE 以允许嵌套会话
      const env = { ...protocolOverrides.env };
      delete env.CLAUDECODE;
      console.log('[factory] buildLaunchOptions: CLAUDECODE deleted');
      return {
        command: protocolOverrides.command || launcherDef.command,
        args: protocolOverrides.args || launcherDef.args || [],
        env: env,
        cwd: workingDir,
      };
    }

    case 'none':
      return { url: launcherDef.url };

    default:
      return {};
  }
}

/**
 * 创建 Transport 并连接
 */
async function createTransport(type, handle, transportConfig = {}) {
  switch (type) {
    case 'pipe': {
      const { PipeTransport } = require('./PipeTransport');
      const transport = new PipeTransport();
      await transport.connect(handle);
      return transport;
    }
    case 'websocket': {
      const { WebSocketTransport } = require('./WebSocketTransport');
      const transport = new WebSocketTransport();
      const url = handle.url || transportConfig.url;
      const timeout = transportConfig.timeout || 10000;
      await transport.connect(url, [], { timeout });
      return transport;
    }
    default:
      throw new Error(`[factory] 未知 transport 类型: ${type}`);
  }
}

module.exports = { createConnection, createLauncher, createTransport, resolveEnvVars };