Ctrl K
ComunidadGuías…

test/devin-acp-model-argv.test.js

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

Archivo original ↗Preguntar a KiBot
import { afterEach, describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { EventEmitter } from 'node:events';
import {
  runDevinAcpProcess,
  __setDevinAcpSpawnForTest,
} from '../src/devin-acp.js';

// Pins the ACP model channel against a NOTE that used to deny it: the
// DEVIN_ONLY note in src/backend-router.js claimed the ACP path "only" passes
// the requested model as a prompt hint. That is false twice over — the model is
// pushed onto the CLI LAUNCH argv as `devin acp --model <key>`
// (src/devin-acp.js:530-531) AND a prompt-side hint is added
// (src/devin-acp.js:579-582). The launch-argv half is machine-checkable here.
//
// The repo proves what it SENDS, not what the `devin` CLI does with it: whether
// `--model` actually switches the CLI's underlying core is not determined by
// this repository and needs a live probe against a real account.
//
// test/mutations/acp-model-argv.json deletes the argv push permanently, so
// this pin cannot decay into a green test that asserts nothing.

// A minimal fake child that looks enough like a ChildProcess for makeAcpClient
// (same shape as test/devin-acp-stdin-epipe.test.js): stdout/stderr/stdin are
// EventEmitters, stdin is writable, kill() is a no-op. It never answers RPCs;
// the spawn seam lets us capture argv before the handshake stalls.
function makeFakeChild() {
  const child = new EventEmitter();
  child.stdout = new EventEmitter();
  child.stderr = new EventEmitter();
  const stdin = new EventEmitter();
  stdin.writable = true;
  stdin.write = () => true;
  child.stdin = stdin;
  child.killed = false;
  child.kill = () => { child.killed = true; return true; };
  return child;
}

function captureSpawn(capture) {
  return (command, args, options) => {
    capture.command = command;
    capture.args = args;
    capture.options = options;
    return makeFakeChild();
  };
}

// Drive one run through the spawn seam. The spawn happens synchronously inside
// runDevinAcpProcess (before its first await), so by the time the promise is
// back the argv is already captured; aborting then settles the never-answered
// initialize RPC so nothing hangs or leaks.
async function runAndCapture(modelKey) {
  const capture = {};
  __setDevinAcpSpawnForTest(captureSpawn(capture));
  const ac = new AbortController();
  const p = runDevinAcpProcess('hi', { modelKey, apiKey: 'k', signal: ac.signal });
  p.catch(() => {}); // the abort rejection is asserted below
  ac.abort();
  await assert.rejects(p);
  return capture;
}

afterEach(() => {
  __setDevinAcpSpawnForTest(null); // restore the real spawn
  delete process.env.DEVIN_CLI_ACP_ARGS_JSON;
});

describe('Devin ACP — the requested model reaches the CLI launch argv', () => {
  it('pushes --model <modelKey> onto the devin acp argv', async () => {
    delete process.env.DEVIN_CLI_ACP_ARGS_JSON; // pin the default parse, not ambient config
    const capture = await runAndCapture('claude-4.5-sonnet');
    assert.equal(capture.command, process.env.DEVIN_CLI_PATH || 'devin');
    assert.deepEqual(
      capture.args,
      ['acp', '--model', 'claude-4.5-sonnet'],
      'the model must reach the CLI launch argv, not only the session/prompt hint',
    );
  });

  it('does not duplicate --model when DEVIN_CLI_ACP_ARGS_JSON already supplies one', async () => {
    process.env.DEVIN_CLI_ACP_ARGS_JSON = JSON.stringify(['acp', '--model', 'operator-pinned-model']);
    const capture = await runAndCapture('claude-4.5-sonnet');
    assert.deepEqual(
      capture.args,
      ['acp', '--model', 'operator-pinned-model'],
      'an operator-supplied --model must win, not be duplicated or overridden',
    );
  });
});