Compare commits

..

1 Commits

Author SHA1 Message Date
Dallin Romney
bb8747122f fix(e2e): keep lifecycle timeout cleanup alive 2026-06-12 18:34:42 -07:00
5 changed files with 43 additions and 200 deletions

View File

@@ -99,11 +99,11 @@ describeLive("moonshot plugin live", () => {
}, 180_000);
});
function resolveMoonshotModels(modelId: string): Model<"openai-completions">[] {
function resolveKimiK27CodeModels(): Model<"openai-completions">[] {
const provider = buildMoonshotProvider();
const model = provider.models.find((entry) => entry.id === modelId);
const model = provider.models.find((entry) => entry.id === "kimi-k2.7-code");
if (!model) {
throw new Error(`Moonshot catalog does not include ${modelId}`);
throw new Error("Moonshot catalog does not include kimi-k2.7-code");
}
const defaultModel = {
provider: "moonshot",
@@ -128,105 +128,18 @@ async function collectDoneMessage(
let doneMessage: AssistantMessage | undefined;
for await (const event of stream) {
if (event.type === "error") {
throw new Error(event.error?.errorMessage || "Moonshot live request failed");
throw new Error(event.error?.errorMessage || "Moonshot K2.7 live request failed");
}
if (event.type === "done") {
doneMessage = event.message;
}
}
if (!doneMessage) {
throw new Error("Moonshot live stream ended without a done message");
throw new Error("Moonshot K2.7 live stream ended without a done message");
}
return doneMessage;
}
describeModelLive("moonshot K2.6 replay live", () => {
it("accepts a cross-model tool-call replay after backfilling reasoning_content", async () => {
const provider = await registerSingleProviderPlugin(plugin);
const wrappedStream = provider.wrapStreamFn?.({
provider: "moonshot",
modelId: "kimi-k2.6",
thinkingLevel: "low",
streamFn: streamSimple,
} as never);
if (!wrappedStream) {
throw new Error("Moonshot provider did not register a stream wrapper");
}
const tool = createNoopTool();
const replayContext: Context = {
messages: [
{
role: "user",
content: "Call the noop tool.",
timestamp: Date.now(),
},
{
role: "assistant",
api: "openai-responses",
provider: "openai",
model: "gpt-5.5",
stopReason: "toolUse",
content: [{ type: "toolCall", id: "call_cross_model", name: "noop", arguments: {} }],
timestamp: Date.now(),
} as AssistantMessage,
{
role: "toolResult",
toolCallId: "call_cross_model",
toolName: "noop",
content: [{ type: "text", text: "ok" }],
isError: false,
timestamp: Date.now(),
},
{
role: "user",
content: "The tool returned ok. Reply with exactly: ok",
timestamp: Date.now(),
},
],
tools: [tool],
};
const runScenario = async (model: Model<"openai-completions">) => {
let payload: Record<string, unknown> | undefined;
const response = await collectDoneMessage(
wrappedStream(model, replayContext, {
apiKey: MOONSHOT_API_KEY,
maxTokens: 256,
onPayload: (value) => {
payload = value as Record<string, unknown>;
},
}) as AsyncIterable<{
type: string;
message?: AssistantMessage;
error?: AssistantMessage;
}>,
);
const messages = payload?.messages as Array<Record<string, unknown>> | undefined;
const replayedAssistant = messages?.find(
(message) => message.role === "assistant" && Array.isArray(message.tool_calls),
);
expect(replayedAssistant?.reasoning_content).toBe("");
expect(response.stopReason).not.toBe("error");
};
let lastAuthError: unknown;
for (const model of resolveMoonshotModels("kimi-k2.6")) {
try {
await runScenario(model);
return;
} catch (error) {
if (!isMoonshotAuthDrift(error)) {
throw error;
}
lastAuthError = error;
}
}
throw toLintErrorObject(lastAuthError, "Moonshot K2.6 rejected the API key in both regions");
}, 180_000);
});
describeModelLive("moonshot K2.7 Code live", () => {
it("omits thinking controls and completes a replayed tool turn", async () => {
const provider = await registerSingleProviderPlugin(plugin);
@@ -331,7 +244,7 @@ describeModelLive("moonshot K2.7 Code live", () => {
};
let lastAuthError: unknown;
for (const model of resolveMoonshotModels("kimi-k2.7-code")) {
for (const model of resolveKimiK27CodeModels()) {
try {
await runScenario(model);
return;

View File

@@ -72,11 +72,13 @@ function readProcSnapshot() {
.trim()
.split(/\s+/u);
const ppid = Number.parseInt(fields[1] ?? "", 10);
const pgrp = Number.parseInt(fields[2] ?? "", 10);
const userTicks = Number.parseInt(fields[11] ?? "", 10);
const systemTicks = Number.parseInt(fields[12] ?? "", 10);
const rssPages = Number.parseInt(fields[21] ?? "", 10);
if (
!Number.isFinite(ppid) ||
!Number.isFinite(pgrp) ||
!Number.isFinite(userTicks) ||
!Number.isFinite(systemTicks) ||
!Number.isFinite(rssPages)
@@ -85,6 +87,7 @@ function readProcSnapshot() {
}
stats.set(pid, {
ppid,
pgrp,
cpuTicks: userTicks + systemTicks,
rssBytes: Math.max(0, rssPages) * pageSize,
});
@@ -117,7 +120,10 @@ function descendantsOf(rootPid, stats) {
function sample(rootPid) {
const stats = readProcSnapshot();
const pids = descendantsOf(rootPid, stats);
const groupPids = new Set(
[...stats.entries()].filter(([, stat]) => stat.pgrp === rootPid).map(([pid]) => pid),
);
const pids = new Set([...descendantsOf(rootPid, stats), ...groupPids]);
let rssBytes = 0;
let cpuTicks = 0;
for (const pid of pids) {
@@ -148,6 +154,10 @@ let forwardedParentSignal = null;
let killTimer;
let parentSignalTimer;
let parentSignalPollTimer;
let childGroupDrainTimer;
// The leader can exit before descendants in its detached process group.
// Keep the wrapper alive so timeout cleanup still owns those descendants.
let childClosedResult = null;
const updateMetrics = () => {
if (!child.pid) {
return;
@@ -157,11 +167,21 @@ const updateMetrics = () => {
maxCpuTicks = Math.max(maxCpuTicks, current.cpuTicks);
};
function finishChildClosedResultIfGroupDrained() {
if (childClosedResult && !childGroupExists()) {
finish(childClosedResult.code, childClosedResult.signal);
}
}
updateMetrics();
const interval = setInterval(updateMetrics, pollMs);
const timeoutTimer =
Number.isFinite(timeoutMs) && timeoutMs > 0
? setTimeout(() => {
if (childClosedResult && !childGroupExists()) {
finish(childClosedResult.code, childClosedResult.signal);
return;
}
timedOut = true;
terminateChildGroup("SIGTERM");
killTimer = setTimeout(() => {
@@ -215,6 +235,9 @@ function clearRuntimeTimers() {
if (parentSignalPollTimer) {
clearInterval(parentSignalPollTimer);
}
if (childGroupDrainTimer) {
clearInterval(childGroupDrainTimer);
}
}
function rethrowParentSignal(signal) {
@@ -329,5 +352,10 @@ child.on("exit", (code, signal) => {
if (timedOut && killTimer) {
return;
}
if (childGroupExists()) {
childClosedResult = { code, signal };
childGroupDrainTimer = setInterval(finishChildClosedResultIfGroupDrained, Math.min(25, pollMs));
return;
}
finish(code, signal);
});

View File

@@ -178,6 +178,7 @@ describe("applyExtraParamsToAgent Moonshot", () => {
expect(payload.thinking).toEqual({ type: "disabled" });
});
it("omits thinking controls and broadens pinned tool choice for kimi-k2.7-code", () => {
const payload = runExtraParamsPayloadCase({
provider: "moonshot",
@@ -185,14 +186,6 @@ describe("applyExtraParamsToAgent Moonshot", () => {
thinkingLevel: "off",
payload: {
model: "kimi-k2.7-code",
messages: [
{
role: "assistant",
tool_calls: [
{ id: "call_1", type: "function", function: { name: "read", arguments: "{}" } },
],
},
],
},
cfg: {
agents: {
@@ -227,63 +220,5 @@ describe("applyExtraParamsToAgent Moonshot", () => {
expect(payload).not.toHaveProperty("n");
expect(payload).not.toHaveProperty("presence_penalty");
expect(payload).not.toHaveProperty("frequency_penalty");
const messages = payload.messages as Array<Record<string, unknown>>;
expect(messages[0].reasoning_content).toBe("");
});
it("repairs only missing assistant tool-call reasoning_content when thinking is enabled", () => {
const payload = runExtraParamsPayloadCase({
provider: "moonshot",
modelId: "kimi-k2.6",
thinkingLevel: "low",
payload: {
model: "kimi-k2.6",
messages: [
{ role: "user", content: "hello" },
{
role: "assistant",
tool_calls: [
{ id: "call_1", type: "function", function: { name: "read", arguments: "{}" } },
],
},
{
role: "assistant",
reasoning_content: "native reasoning",
tool_calls: [
{ id: "call_2", type: "function", function: { name: "read", arguments: "{}" } },
],
},
{ role: "assistant", content: "done" },
{ role: "tool", tool_call_id: "call_1", content: "file contents" },
],
},
});
expect(payload.thinking).toEqual({ type: "enabled" });
const messages = payload.messages as Array<Record<string, unknown>>;
expect(messages[1].reasoning_content).toBe("");
expect(messages[2].reasoning_content).toBe("native reasoning");
expect(messages[3]).not.toHaveProperty("reasoning_content");
});
it("does not backfill reasoning_content when thinking is disabled", () => {
const payload = runExtraParamsPayloadCase({
provider: "moonshot",
modelId: "kimi-k2.5",
thinkingLevel: "off",
payload: {
messages: [
{
role: "assistant",
tool_calls: [
{ id: "call_1", type: "function", function: { name: "read", arguments: "{}" } },
],
},
],
},
});
const messages = payload.messages as Array<Record<string, unknown>>;
expect(messages[0].reasoning_content).toBeUndefined();
});
});

View File

@@ -81,23 +81,6 @@ function asPayloadRecord(value: unknown): Record<string, unknown> | undefined {
: undefined;
}
function ensureMoonshotToolCallReasoningContent(payloadObj: Record<string, unknown>): void {
if (!Array.isArray(payloadObj.messages)) {
return;
}
for (const message of payloadObj.messages) {
const record = asPayloadRecord(message);
if (
record?.role === "assistant" &&
Array.isArray(record.tool_calls) &&
record.tool_calls.length > 0 &&
!("reasoning_content" in record)
) {
record.reasoning_content = "";
}
}
}
function sanitizeKimiK27Payload(payloadObj: Record<string, unknown>): void {
delete payloadObj.thinking;
delete payloadObj.reasoning_effort;
@@ -114,20 +97,7 @@ function sanitizeKimiK27AfterCaller(
value: unknown,
fallbackPayload: Record<string, unknown>,
): unknown {
const finalPayload = asPayloadRecord(value) ?? fallbackPayload;
sanitizeKimiK27Payload(finalPayload);
ensureMoonshotToolCallReasoningContent(finalPayload);
return value;
}
function finalizeMoonshotPayloadAfterCaller(
value: unknown,
fallbackPayload: Record<string, unknown>,
thinkingEnabled: boolean,
): unknown {
if (thinkingEnabled) {
ensureMoonshotToolCallReasoningContent(asPayloadRecord(value) ?? fallbackPayload);
}
sanitizeKimiK27Payload(asPayloadRecord(value) ?? fallbackPayload);
return value;
}
@@ -223,14 +193,7 @@ export function createMoonshotThinkingWrapper(
delete thinkingObj.keep;
}
}
const result = originalOnPayload?.(payload, payloadModel);
const thinkingEnabled = effectiveThinkingType === "enabled";
if (result && typeof (result as Promise<unknown>).then === "function") {
return Promise.resolve(result).then((resolved) =>
finalizeMoonshotPayloadAfterCaller(resolved, payloadObj, thinkingEnabled),
);
}
return finalizeMoonshotPayloadAfterCaller(result, payloadObj, thinkingEnabled);
return originalOnPayload?.(payload, payloadModel);
},
});
};

View File

@@ -220,7 +220,11 @@ describe("plugin lifecycle resource sampler", () => {
"--",
"bash",
"-lc",
'bash -c \'trap "" TERM; printf "%s\\n" "$$" >"$PID_FILE"; while :; do sleep 1; done\' & wait',
[
'bash -c \'trap "" TERM; printf "%s\\n" "$$" >"$PID_FILE"; while :; do sleep 1; done\' &',
'while [ ! -s "$PID_FILE" ]; do sleep 0.01; done',
"exit 0",
].join("\n"),
],
{
cwd: process.cwd(),