mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
test(ci): repair release validation gates (#108646)
* test(agents): harden Z.AI live output probe * test(agents): classify live provider failures * test(ci): strengthen package export contract * chore(protocol): regenerate Swift session create params
This commit is contained in:
committed by
GitHub
parent
73ada8fd4c
commit
b23292cc3f
@@ -9,6 +9,7 @@ import type { Model } from "openclaw/plugin-sdk/llm";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createAnthropicMessagesTransportStreamFn } from "./anthropic-transport-stream.js";
|
||||
import { isLiveTestEnabled } from "./live-test-helpers.js";
|
||||
import { shouldSkipLiveProviderDrift } from "./live-test-provider-drift.js";
|
||||
import { isLiveBillingDrift } from "./live-test-provider-drift.test-support.js";
|
||||
|
||||
const LIVE = isLiveTestEnabled(["ANTHROPIC_TRANSPORT_LIVE_TEST"]);
|
||||
@@ -79,6 +80,23 @@ function skipAnthropicBillingDrift(
|
||||
return true;
|
||||
}
|
||||
|
||||
function classifyProviderError(errorMessage: string | undefined): string {
|
||||
if (!errorMessage) {
|
||||
return "none";
|
||||
}
|
||||
return (
|
||||
shouldSkipLiveProviderDrift({
|
||||
allowAuth: true,
|
||||
allowBilling: true,
|
||||
allowModelNotFound: true,
|
||||
allowProviderUnavailable: true,
|
||||
allowRateLimit: true,
|
||||
allowTimeout: true,
|
||||
error: errorMessage,
|
||||
})?.reason ?? "unclassified"
|
||||
);
|
||||
}
|
||||
|
||||
describeLive("anthropic transport stream live", () => {
|
||||
it("cancels an in-flight SSE body read over a real HTTP stream", async () => {
|
||||
const controller = new AbortController();
|
||||
@@ -217,7 +235,10 @@ describeOpusTupleLive("anthropic Opus tuple schema provider live", () => {
|
||||
(block) => block.type === "toolCall" && block.name === "tuple_probe",
|
||||
);
|
||||
|
||||
expect(result.stopReason).toBe("toolUse");
|
||||
expect(
|
||||
result.stopReason,
|
||||
`tuple tool projection failed; errorClass=${classifyProviderError(result.errorMessage)}`,
|
||||
).toBe("toolUse");
|
||||
expect(toolCall).toMatchObject({
|
||||
type: "toolCall",
|
||||
name: "tuple_probe",
|
||||
@@ -282,7 +303,10 @@ describeProviderLive("anthropic transport stream provider live", () => {
|
||||
(block) => block.type === "toolCall" && block.name === "healthy_probe",
|
||||
);
|
||||
|
||||
expect(result.stopReason).toBe("toolUse");
|
||||
expect(
|
||||
result.stopReason,
|
||||
`forced tool projection failed; errorClass=${classifyProviderError(result.errorMessage)}`,
|
||||
).toBe("toolUse");
|
||||
expect(toolCall).toMatchObject({
|
||||
type: "toolCall",
|
||||
name: "healthy_probe",
|
||||
@@ -344,7 +368,10 @@ describeProviderLive("anthropic transport stream provider live", () => {
|
||||
(block) => block.type === "toolCall" && block.name === "healthy_probe",
|
||||
);
|
||||
|
||||
expect(result.stopReason).toBe("toolUse");
|
||||
expect(
|
||||
result.stopReason,
|
||||
`SDK forced tool projection failed; errorClass=${classifyProviderError(result.errorMessage)}`,
|
||||
).toBe("toolUse");
|
||||
expect(toolCall).toMatchObject({
|
||||
type: "toolCall",
|
||||
name: "healthy_probe",
|
||||
|
||||
+33
-11
@@ -8,6 +8,7 @@ import {
|
||||
extractNonEmptyAssistantText,
|
||||
isLiveTestEnabled,
|
||||
} from "./live-test-helpers.js";
|
||||
import { shouldSkipLiveProviderDrift } from "./live-test-provider-drift.js";
|
||||
|
||||
const ZAI_KEY = process.env.ZAI_API_KEY ?? process.env.Z_AI_API_KEY ?? "";
|
||||
const LIVE = isLiveTestEnabled(["ZAI_LIVE_TEST"]);
|
||||
@@ -35,17 +36,38 @@ async function expectModelReturnsAssistantText(
|
||||
contextWindow: modelId === "glm-5.2" ? 1_000_000 : 202_800,
|
||||
maxTokens: modelId === "glm-5.2" ? 131_072 : 131_100,
|
||||
};
|
||||
// GLM-5 reasoning is enabled by default and consumes the same output budget.
|
||||
// Keep enough headroom for a visible answer while retaining a bounded probe.
|
||||
const res = await completeSimple(
|
||||
model,
|
||||
{
|
||||
messages: createSingleUserPromptMessage(),
|
||||
},
|
||||
{ apiKey: ZAI_KEY, maxTokens: 1_024 },
|
||||
);
|
||||
const text = extractNonEmptyAssistantText(res.content);
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
const complete = (maxTokens: number) =>
|
||||
completeSimple(
|
||||
model,
|
||||
{
|
||||
messages: createSingleUserPromptMessage(),
|
||||
},
|
||||
{ apiKey: ZAI_KEY, maxTokens },
|
||||
);
|
||||
|
||||
// A small probe cap can occasionally yield only hidden reasoning even though
|
||||
// production allows much more output. Retry once, but still require visible text.
|
||||
const initial = await complete(1_024);
|
||||
let final = initial;
|
||||
let text = extractNonEmptyAssistantText(final.content);
|
||||
if (!text && (initial.stopReason === "stop" || initial.stopReason === "length")) {
|
||||
final = await complete(8_192);
|
||||
text = extractNonEmptyAssistantText(final.content);
|
||||
}
|
||||
const drift = shouldSkipLiveProviderDrift({
|
||||
allowAuth: true,
|
||||
allowBilling: true,
|
||||
allowModelNotFound: true,
|
||||
allowProviderUnavailable: true,
|
||||
allowRateLimit: true,
|
||||
allowTimeout: true,
|
||||
error: final.errorMessage ?? "",
|
||||
});
|
||||
const errorClass = final.errorMessage ? (drift?.reason ?? "unclassified") : "none";
|
||||
expect(
|
||||
text.length,
|
||||
`${modelId} returned no assistant text; initialStopReason=${initial.stopReason}; finalStopReason=${final.stopReason}; errorClass=${errorClass}; contentTypes=${final.content.map((block) => block.type).join(",") || "none"}`,
|
||||
).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
describeCodingLive("zai Coding Plan live", () => {
|
||||
|
||||
@@ -421,10 +421,16 @@ describe("Parallels smoke model selection", () => {
|
||||
|
||||
expect(common).toContain('export * from "./host-command.ts"');
|
||||
expect(common).toContain('export * from "./lane-runner.ts"');
|
||||
// The deadcode hard-zero sweep narrowed this barrel to named exports; the
|
||||
// shared contract is that package helpers stay routed through common.
|
||||
expect(common).toContain('} from "./package-artifact.ts"');
|
||||
expect(common).toContain("packOpenClaw");
|
||||
const packageArtifactExports = new Set(
|
||||
(common.match(/export \{([^}]*)\} from "\.\/package-artifact\.ts";/)?.[1] ?? "")
|
||||
.split(",")
|
||||
.map((name) => name.trim())
|
||||
.filter(Boolean),
|
||||
);
|
||||
expect(packageArtifactExports).toContain("packOpenClaw");
|
||||
expect(packageArtifactExports).toContain("packageVersionFromTgz");
|
||||
expect(packageArtifactExports).toContain("resolveOpenClawRegistryVersion");
|
||||
expect(common).not.toContain('export * from "./package-artifact.ts"');
|
||||
expect(common).toContain('export * from "./parallels-vm.ts"');
|
||||
expect(common).toContain('export * from "./snapshots.ts"');
|
||||
expect(hostCommand).toContain("export function shellQuote");
|
||||
|
||||
Reference in New Issue
Block a user