Ctrl K
ComunidadGuías…

test/antigravity-adapter.test.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
var test = require("node:test");
var assert = require("node:assert");
var EventEmitter = require("node:events");
var PassThrough = require("node:stream").PassThrough;

var antigravity = require("../lib/yoke/adapters/antigravity");

function createFakeSpawn(calls) {
  return function(binary, args, options) {
    var proc = new EventEmitter();
    proc.stdout = new PassThrough();
    proc.stderr = new PassThrough();
    proc.stdin = new EventEmitter();
    proc.stdin.write = function(line) {
      calls.push({ input: JSON.parse(line) });
      setImmediate(function() {
        proc.stdout.write(JSON.stringify({ event: "init", conversation_id: "agy-session" }) + "\n");
        proc.stdout.write(JSON.stringify({
          event: "step_update",
          step_update: { step_index: 1, state: "ACTIVE", step_type: "agent_response", text_delta: "hello" },
        }) + "\n");
        proc.stdout.write(JSON.stringify({
          event: "step_update",
          step_update: {
            step_index: 2,
            state: "DONE",
            step_type: "tool",
            tool_name: "run_command",
            tool_info: { name: "run_command", parameters: { CommandLine: "pwd" }, output: "/project\n" },
          },
        }) + "\n");
        proc.stdout.write(JSON.stringify({
          event: "result",
          result: {
            conversation_id: "agy-session",
            status: "SUCCESS",
            duration_seconds: 1.5,
            usage: { input_tokens: 10, output_tokens: 2, cache_read_tokens: 4 },
          },
        }) + "\n");
      });
      return true;
    };
    proc.stdin.end = function() {
      setImmediate(function() { proc.emit("exit", 0, null); });
    };
    proc.kill = function() { proc.emit("exit", null, "SIGINT"); };
    calls.push({ binary: binary, args: args, options: options });
    return proc;
  };
}

test("Antigravity model parser accepts official JSON and text listings", function() {
  assert.deepStrictEqual(antigravity.parseModels(JSON.stringify({
    models: [{ id: "gemini-pro" }, { slug: "gemini-flash" }],
  })), ["gemini-pro", "gemini-flash"]);
  assert.deepStrictEqual(antigravity.parseModels("gemini-pro  Gemini Pro\ngemini-flash  Gemini Flash\n"), ["gemini-pro", "gemini-flash"]);
});

test("Antigravity adapter uses the official streaming CLI protocol", async function() {
  var calls = [];
  var adapter = antigravity.createAntigravityAdapter({
    cwd: "/project",
    _binaryPath: "/contract/agy",
    _fetchModels: function() { return Promise.resolve(["gemini-pro"]); },
    _spawn: createFakeSpawn(calls),
  });
  var ready = await adapter.init();
  assert.deepStrictEqual(ready.models, ["gemini-pro"]);
  assert.strictEqual(ready.capabilities.sessionResume, true);
  assert.strictEqual(ready.capabilities.effort, true);
  assert.deepStrictEqual(ready.capabilities.queryBoundTools, {
    mode: "none",
    reason: "The current Clay adapter does not forward custom-tool configuration to agy.",
  });
  assert.strictEqual(adapter.createToolServer({ tools: [] }), null);

  var handle = await adapter.createQuery({
    cwd: "/project",
    model: "gemini-pro",
    effort: "high",
    resumeSessionId: "previous-session",
    systemPrompt: "Project instructions",
    env: { PROJECT_TOKEN: "scoped" },
    dynamicTools: [{ name: "probe" }],
    toolServers: { probe: { command: "probe" } },
    adapterOptions: { ANTIGRAVITY: { dangerouslySkipPermissions: true } },
  });
  assert.strictEqual(handle.pushMessage("Say hello"), true);
  handle.endInput();
  var events = [];
  for await (var event of handle) events.push(event);

  assert.deepStrictEqual(calls[0].args, [
    "--input-format", "stream-json",
    "--output-format", "stream-json",
    "--conversation", "previous-session",
    "--model", "gemini-pro",
    "--effort", "high",
    "--dangerously-skip-permissions",
  ]);
  assert.strictEqual(calls[1].input.message.content, "Project instructions\n\nSay hello");
  assert.strictEqual(calls[0].options.env.PROJECT_TOKEN, "scoped");
  assert.ok(events.some(function(event) { return event.yokeType === "text_delta" && event.text === "hello"; }));
  assert.ok(events.some(function(event) { return event.yokeType === "tool_start" && event.toolName === "Bash"; }));
  assert.ok(events.some(function(event) { return event.yokeType === "tool_result" && event.content === "/project\n"; }));
  assert.ok(events.some(function(event) { return event.yokeType === "result" && event.sessionId === "agy-session"; }));
  await adapter.shutdown();
});

test("Antigravity reports per-turn usage from cumulative stream results", async function() {
  var turn = 0;
  function fakeSpawn() {
    var proc = new EventEmitter();
    proc.stdout = new PassThrough();
    proc.stderr = new PassThrough();
    proc.stdin = new EventEmitter();
    proc.stdin.write = function() {
      turn++;
      var current = turn;
      setImmediate(function() {
        proc.stdout.write(JSON.stringify({ event: "step_update", step_update: {
          step_index: current, state: "DONE", step_type: "agent_response", text_delta: current === 1 ? "one" : "two",
          usage: current === 1
            ? { input_tokens: 10, output_tokens: 2, cache_read_tokens: 1 }
            : { input_tokens: 4, output_tokens: 3, cache_read_tokens: 0 },
          duration_seconds: current === 1 ? 1.25 : 0.75,
        } }) + "\n");
        proc.stdout.write(JSON.stringify({ event: "result", result: {
          status: "SUCCESS", conversation_id: "multi",
          usage: current === 1
            ? { input_tokens: 10, output_tokens: 2, cache_read_tokens: 1 }
            : { input_tokens: 14, output_tokens: 5, cache_read_tokens: 1 },
          duration_seconds: current === 1 ? 1.25 : 2,
        } }) + "\n");
      });
      return true;
    };
    proc.stdin.end = function() { setImmediate(function() { proc.emit("exit", 0, null); }); };
    proc.kill = function() { proc.emit("exit", null, "SIGINT"); };
    return proc;
  }
  var handle = antigravity.createAntigravityQueryHandle("/contract/agy", { cwd: "/project", _spawn: fakeSpawn }, function() {});
  handle.pushMessage("first");
  var results = [];
  for await (var firstEvent of handle) {
    if (firstEvent.yokeType === "result") { results.push(firstEvent); break; }
  }
  handle.pushMessage("second");
  handle.endInput();
  for await (var laterEvent of handle) {
    if (laterEvent.yokeType === "result") results.push(laterEvent);
  }
  assert.deepStrictEqual(results.map(function(result) { return result.usage; }), [{
    input_tokens: 10, output_tokens: 2, cache_read_input_tokens: 1, cache_creation_input_tokens: 0,
  }, {
    input_tokens: 4, output_tokens: 3, cache_read_input_tokens: 0, cache_creation_input_tokens: 0,
  }]);
  assert.deepStrictEqual(results.map(function(result) { return result.duration; }), [1250, 750]);
  assert.deepStrictEqual(await handle.getContextUsage(), { input_tokens: 15, contextWindow: null });
});

test("Antigravity sums completed step usage for the first resumed turn", async function() {
  function fakeSpawn() {
    var proc = new EventEmitter();
    proc.stdout = new PassThrough();
    proc.stderr = new PassThrough();
    proc.stdin = new EventEmitter();
    proc.stdin.write = function() {
      setImmediate(function() {
        var response = { event: "step_update", step_update: {
          step_index: 1, state: "DONE", step_type: "agent_response", text_delta: "done",
          usage: { input_tokens: 10, output_tokens: 2, cache_read_tokens: 1 }, duration_seconds: 1,
        } };
        proc.stdout.write(JSON.stringify(response) + "\n");
        proc.stdout.write(JSON.stringify(response) + "\n");
        proc.stdout.write(JSON.stringify({ event: "step_update", step_update: {
          step_index: 2, state: "DONE", step_type: "checkpoint",
          usage: { input_tokens: 3, output_tokens: 1, cache_read_tokens: 2 }, duration_seconds: 0.25,
        } }) + "\n");
        proc.stdout.write(JSON.stringify({ event: "result", result: {
          status: "SUCCESS", conversation_id: "resumed",
          duration_seconds: 10.25,
        } }) + "\n");
      });
      return true;
    };
    proc.stdin.end = function() { setImmediate(function() { proc.emit("exit", 0, null); }); };
    proc.kill = function() { proc.emit("exit", null, "SIGINT"); };
    return proc;
  }
  var handle = antigravity.createAntigravityQueryHandle("/contract/agy", {
    cwd: "/project", resumeSessionId: "existing", _spawn: fakeSpawn,
  }, function() {});
  handle.pushMessage("continue");
  handle.endInput();
  var result = null;
  for await (var event of handle) if (event.yokeType === "result") result = event;
  assert.deepStrictEqual(result.usage, {
    input_tokens: 13, output_tokens: 3, cache_read_input_tokens: 3, cache_creation_input_tokens: 0,
  });
  assert.strictEqual(result.duration, 1250);
});

test("Antigravity leaves an unmeasurable resumed first turn unknown and baselines the next", async function() {
  var turn = 0;
  function fakeSpawn() {
    var proc = new EventEmitter();
    proc.stdout = new PassThrough();
    proc.stderr = new PassThrough();
    proc.stdin = new EventEmitter();
    proc.stdin.write = function() {
      turn++;
      var current = turn;
      setImmediate(function() {
        proc.stdout.write(JSON.stringify({ event: "result", result: {
          status: "SUCCESS", conversation_id: "resumed",
          usage: current === 1
            ? { input_tokens: 100, output_tokens: 50, cache_read_tokens: 20 }
            : { input_tokens: 104, output_tokens: 53, cache_read_tokens: 21 },
          duration_seconds: current === 1 ? 10 : 10.75,
        } }) + "\n");
      });
      return true;
    };
    proc.stdin.end = function() { setImmediate(function() { proc.emit("exit", 0, null); }); };
    proc.kill = function() { proc.emit("exit", null, "SIGINT"); };
    return proc;
  }
  var handle = antigravity.createAntigravityQueryHandle("/contract/agy", {
    cwd: "/project", resumeSessionId: "existing", _spawn: fakeSpawn,
  }, function() {});
  handle.pushMessage("first resumed turn");
  var results = [];
  for await (var firstEvent of handle) {
    if (firstEvent.yokeType === "result") { results.push(firstEvent); break; }
  }
  handle.pushMessage("second resumed turn");
  handle.endInput();
  for await (var laterEvent of handle) if (laterEvent.yokeType === "result") results.push(laterEvent);
  assert.strictEqual(results[0].usage, null);
  assert.strictEqual(results[0].duration, null);
  assert.deepStrictEqual(results[1].usage, {
    input_tokens: 4, output_tokens: 3, cache_read_input_tokens: 1, cache_creation_input_tokens: 0,
  });
  assert.strictEqual(results[1].duration, 750);
  assert.deepStrictEqual(await handle.getContextUsage(), { input_tokens: 125, contextWindow: null });
});