test/connect-stop-reason-map.test.js
Rama master · réplica de lectura del repositorio original.
// P1 (Q7 descriptor audit): the old table mapped every StopReason to 'stop',
// so a native tool-call turn, a max-token truncation, a content filter and an
// upstream ERROR were all reported to clients as a clean completion.
//
// The table below is the enum as defined by the binary descriptor set
// (exa.codeium_common_pb.StopReason, 0–13, extracted in the Q7 audit from
// leookun/devin-2api @ e7ddc55 `descriptors.pb`). The independent Go decoder
// (internal/adapter/devin/response_decoder.go) corroborates the non-trivial
// rows: INCOMPLETE/MAX_TOKENS/PARTIAL → length, FUNCTION_CALL → tool_use,
// ERROR → error.
//
// 2 and 4 additionally have LIVE anchors (2 = free-tier clean stop, 4 = paid
// clean stop, 2026-07-27 calibration); the descriptor does not contradict them.
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { mapFinishReason, resolveFinishReason } from '../src/devin-connect.js';
import { log } from '../src/config.js';
const DESCRIPTOR_STOP_REASON = {
0: 'stop', // STOP_REASON_UNSPECIFIED
1: 'length', // INCOMPLETE
2: 'stop', // STOP_PATTERN (live-anchored clean stop)
3: 'length', // MAX_TOKENS
4: 'stop', // MIN_LOG_PROB (live-anchored paid clean stop)
5: 'length', // MAX_NEWLINES
6: 'stop', // EXIT_SCOPE
7: 'stop', // NONFINITE_LOGIT_OR_PROB
8: 'stop', // FIRST_NON_WHITESPACE_LINE
9: 'length', // PARTIAL
10: 'tool_calls', // FUNCTION_CALL
11: 'content_filter', // CONTENT_FILTER
12: 'stop', // NON_INSERTION
13: 'error', // ERROR
};
describe('StopReason → OpenAI finish_reason (descriptor-backed)', () => {
it('maps every enum value 0–13 exactly once, as Number and as BigInt', () => {
for (const [raw, want] of Object.entries(DESCRIPTOR_STOP_REASON)) {
const n = Number(raw);
assert.equal(mapFinishReason(n, {}), want, `StopReason ${n} must map to ${want}`);
assert.equal(mapFinishReason(BigInt(n), {}), want,
`the BigInt form of ${n} must map identically (proto varints decode as BigInt)`);
}
});
it('FUNCTION_CALL (10) reports tool_calls, not a clean stop', () => {
assert.equal(mapFinishReason(10, {}), 'tool_calls');
});
it('MAX_TOKENS (3) reports length, not a clean stop', () => {
assert.equal(mapFinishReason(3, {}), 'length');
});
it('CONTENT_FILTER (11) reports content_filter, not a clean stop', () => {
assert.equal(mapFinishReason(11, {}), 'content_filter');
});
it('ERROR (13) reports error, not a clean stop', () => {
assert.equal(mapFinishReason(13, {}), 'error');
});
it('null means "no reason yet", not a stop', () => {
assert.equal(mapFinishReason(null, {}), null);
});
it('unknown values fall back to stop and log once per distinct value', () => {
const warns = [];
const original = log.warn;
log.warn = (...args) => warns.push(args.join(' '));
try {
assert.equal(mapFinishReason(424242, {}), 'stop');
assert.equal(mapFinishReason(424242, {}), 'stop');
assert.equal(mapFinishReason(424242n, {}), 'stop');
} finally {
log.warn = original;
}
assert.equal(warns.length, 1, 'one line per unknown value, not one per frame');
assert.match(warns[0], /424242/, 'the log line must name the value for diagnosis');
});
it('an operator override still wins over the descriptor table', () => {
const env = { DEVIN_CONNECT_STOP_REASON_MAP: '13=stop,3=stop,10=content_filter' };
assert.equal(mapFinishReason(13, env), 'stop');
assert.equal(mapFinishReason(3, env), 'stop');
assert.equal(mapFinishReason(10, env), 'content_filter');
});
it('the usage-based truncation inference never overwrites a stronger reason', () => {
// A FUNCTION_CALL / CONTENT_FILTER / ERROR turn that happens to land on the
// token cap is still a tool call / refusal / failure, not a truncation.
for (const reason of [10, 11, 13]) {
const mapped = mapFinishReason(reason, {});
assert.equal(resolveFinishReason(reason, { completion_tokens: 64 }, 64, {}), mapped);
}
// A clean-stop enum still upgrades on a genuine cap hit.
assert.equal(resolveFinishReason(2, { completion_tokens: 64 }, 64, {}), 'length');
});
});