ci: harden live release gates

This commit is contained in:
Peter Steinberger
2026-05-26 16:31:30 +01:00
parent c84f61cd2e
commit cf21c8abcb
5 changed files with 28 additions and 22 deletions

View File

@@ -2295,7 +2295,7 @@ jobs:
profiles: beta minimum stable full
- suite_id: live-gateway-anthropic-docker
label: Docker live gateway Anthropic
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MODELS=anthropic/claude-sonnet-4-6 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
profile_env_only: false
profiles: stable full

View File

@@ -10,11 +10,11 @@ type ModelRef = {
};
const HIGH_SIGNAL_LIVE_MODEL_PRIORITY = [
"anthropic/claude-opus-4-7",
"anthropic/claude-opus-4-6",
"anthropic/claude-sonnet-4-6",
"anthropic/claude-opus-4-7",
"google/gemini-3.1-pro-preview",
"google/gemini-3-flash-preview",
"anthropic/claude-opus-4-6",
"deepseek/deepseek-v4-flash",
"deepseek/deepseek-v4-pro",
"minimax/minimax-m2.7",

View File

@@ -656,11 +656,11 @@ describe("isPrioritizedHighSignalLiveModelRef", () => {
it("lists priority refs as provider/id pairs", () => {
expect(listPrioritizedHighSignalLiveModelRefs()).toStrictEqual([
{ provider: "anthropic", id: "claude-opus-4-7" },
{ provider: "anthropic", id: "claude-opus-4-6" },
{ provider: "anthropic", id: "claude-sonnet-4-6" },
{ provider: "anthropic", id: "claude-opus-4-7" },
{ provider: "google", id: "gemini-3.1-pro-preview" },
{ provider: "google", id: "gemini-3-flash-preview" },
{ provider: "anthropic", id: "claude-opus-4-6" },
{ provider: "deepseek", id: "deepseek-v4-flash" },
{ provider: "deepseek", id: "deepseek-v4-pro" },
{ provider: "minimax", id: "minimax-m2.7" },
@@ -682,6 +682,7 @@ describe("isPrioritizedHighSignalLiveModelRef", () => {
describe("selectHighSignalLiveItems", () => {
it("prefers curated Google replacements before fallback provider spread", () => {
const items = [
{ provider: "anthropic", id: "claude-sonnet-4-6" },
{ provider: "anthropic", id: "claude-opus-4-7" },
{ provider: "anthropic", id: "claude-opus-4-6" },
{ provider: "google", id: "gemini-3.1-pro-preview" },
@@ -699,8 +700,8 @@ describe("selectHighSignalLiveItems", () => {
(item) => item.provider,
),
).toEqual([
{ provider: "anthropic", id: "claude-sonnet-4-6" },
{ provider: "anthropic", id: "claude-opus-4-7" },
{ provider: "anthropic", id: "claude-opus-4-6" },
{ provider: "google", id: "gemini-3.1-pro-preview" },
{ provider: "google", id: "gemini-3-flash-preview" },
]);

View File

@@ -369,12 +369,12 @@ function resolveLiveModelsJsonTimeoutMs(
modelsJsonTimeoutRaw?: string,
setupTimeoutMs = LIVE_SETUP_TIMEOUT_MS,
): number {
return Math.max(setupTimeoutMs, toInt(modelsJsonTimeoutRaw, 120_000));
return Math.max(setupTimeoutMs, toInt(modelsJsonTimeoutRaw, 180_000));
}
describe("resolveLiveModelsJsonTimeoutMs", () => {
it("defaults models.json preparation to a longer setup timeout", () => {
expect(resolveLiveModelsJsonTimeoutMs(undefined, 45_000)).toBe(120_000);
expect(resolveLiveModelsJsonTimeoutMs(undefined, 45_000)).toBe(180_000);
});
it("never goes below the shared live setup timeout", () => {

View File

@@ -62,6 +62,23 @@ function isOpenAIResponsesFamily(api: string): boolean {
);
}
function createNoopTools() {
return [
{
name: "noop",
description: "Return ok.",
parameters: Type.Object({}, { additionalProperties: false }),
},
];
}
function replayValidationTools(model: Model<Api>) {
// Responses-family providers may force a new tool call whenever tools are
// present. These live probes validate repaired historical transcript shape,
// not fresh tool invocation.
return isOpenAIResponsesFamily(model.api) ? undefined : createNoopTools();
}
function buildReplayMessages(model: Model<Api>): AgentMessage[] {
const now = Date.now();
// Gemini source metadata deliberately simulates a model switch from a
@@ -253,13 +270,7 @@ describeLive("tool replay repair live", () => {
{
systemPrompt: "You are a concise assistant. Follow the user's instruction exactly.",
messages: sanitized as never,
tools: [
{
name: "noop",
description: "Return ok.",
parameters: Type.Object({}, { additionalProperties: false }),
},
],
tools: replayValidationTools(model),
},
{
apiKey: requireApiKey(apiKeyInfo, model.provider),
@@ -334,13 +345,7 @@ describeLive("tool replay repair live", () => {
{
systemPrompt: "You are a concise assistant. Follow the user's instruction exactly.",
messages: transformed as never,
tools: [
{
name: "noop",
description: "Return ok.",
parameters: Type.Object({}, { additionalProperties: false }),
},
],
tools: replayValidationTools(model),
},
{
apiKey: requireApiKey(apiKeyInfo, model.provider),