Agent Sandbox Registry

awesome-providers

opensandbox/tests/epp-client.mjs

无中文版

opensandbox/tests/epp-client.mjs

// Minimal EPP/1 host used by the tests: spawns the plugin exactly like a real host would.

import { spawn } from "node:child_process";
import { createInterface } from "node:readline";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

export const PLUGIN_DIR = join(dirname(fileURLToPath(import.meta.url)), "..");

export class EppHost {
  constructor({ cwd = PLUGIN_DIR, env } = {}) {
    this.proc = spawn(process.execPath, [join(PLUGIN_DIR, "provider.mjs")], {
      cwd,
      env: env ?? { PATH: process.env.PATH },
      stdio: ["pipe", "pipe", "pipe"],
    });
    this.nextId = 0;
    this.pending = new Map();
    this.notes = [];
    this.waiters = [];
    this.stderr = "";
    this.proc.stderr.on("data", (d) => { this.stderr += d; });
    this.exited = new Promise((resolve) => this.proc.on("exit", (code) => resolve(code)));
    createInterface({ input: this.proc.stdout }).on("line", (line) => {
      const msg = JSON.parse(line);
      if (msg.id !== undefined && msg.id !== null && this.pending.has(msg.id)) {
        const { resolve, reject } = this.pending.get(msg.id);
        this.pending.delete(msg.id);
        if (msg.error) reject(Object.assign(new Error(msg.error.message), { rpc: msg.error }));
        else resolve(msg.result);
      } else if (msg.method) {
        this.notes.push(msg);
        for (const w of this.waiters.slice()) w();
      }
    });
  }

  call(method, params = {}) {
    const id = ++this.nextId;
    return new Promise((resolve, reject) => {
      this.pending.set(id, { resolve, reject });
      this.proc.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`);
    });
  }

  raw(line) {
    this.proc.stdin.write(`${line}\n`);
  }

  /** Resolve once a notification matching `pred` has arrived (already-received ones count). */
  waitNote(pred, timeoutMs = 20_000) {
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => reject(new Error(`timed out waiting for notification; got ${JSON.stringify(this.notes)}`)), timeoutMs);
      const check = () => {
        const hit = this.notes.find(pred);
        if (hit) {
          clearTimeout(timer);
          this.waiters = this.waiters.filter((w) => w !== check);
          resolve(hit);
          return true;
        }
        return false;
      };
      if (!check()) this.waiters.push(check);
    });
  }

  async initialize(config = {}, core = {}) {
    return this.call("initialize", {
      protocol: "epp/1",
      host: { name: "test-host", version: "0" },
      provider_id: "opensandbox",
      config,
      core,
      locale: "en",
      data_dir: process.cwd(),
    });
  }

  async close() {
    try {
      await this.call("shutdown");
    } catch {
      /* already gone */
    }
    this.proc.stdin.end();
    const timer = setTimeout(() => this.proc.kill("SIGKILL"), 3000);
    const code = await this.exited;
    clearTimeout(timer);
    return code;
  }
}