test(live): tolerate ARM provider drift

This commit is contained in:
Vincent Koc
2026-06-06 03:47:24 -07:00
parent f4a5e5762e
commit 74331f632b
8 changed files with 60 additions and 5 deletions

View File

@@ -6,6 +6,7 @@ import {
import { normalizeTranscriptForMatch } from "openclaw/plugin-sdk/provider-test-contracts";
import { isLiveTestEnabled } from "openclaw/plugin-sdk/test-env";
import { describe, expect, it } from "vitest";
import { hasTrustedFfmpegForLiveVoiceNote } from "../../test/helpers/live-voice-note.js";
import plugin from "./index.js";
import { createGeminiWebSearchProvider } from "./src/gemini-web-search-provider.js";
@@ -75,6 +76,10 @@ describeLive("google plugin live", () => {
}, 120_000);
it("transcodes speech to Opus for voice-note targets", async () => {
if (!hasTrustedFfmpegForLiveVoiceNote("google")) {
return;
}
const { speechProviders } = await registerGooglePlugin();
const provider = requireRegisteredProvider(speechProviders, "google");

View File

@@ -5,6 +5,7 @@ import {
} from "openclaw/plugin-sdk/plugin-test-runtime";
import { isLiveTestEnabled } from "openclaw/plugin-sdk/test-env";
import { describe, expect, it } from "vitest";
import { hasTrustedFfmpegForLiveVoiceNote } from "../../test/helpers/live-voice-note.js";
import plugin from "./index.js";
import { buildMinimaxSpeechProvider } from "./speech-provider.js";
import { createMiniMaxWebSearchProvider } from "./src/minimax-web-search-provider.js";
@@ -70,6 +71,10 @@ describeTtsLive("minimax tts live", () => {
}, 120_000);
it("synthesizes MiniMax TTS as an Opus voice note", async () => {
if (!hasTrustedFfmpegForLiveVoiceNote("minimax")) {
return;
}
const provider = buildMinimaxSpeechProvider();
const voiceNote = await provider.synthesize({

View File

@@ -19,6 +19,17 @@ function isTransientKimiSearchError(error: unknown): boolean {
return message.includes("timeout") || message.includes("aborted");
}
function isKimiAuthDrift(error: unknown): boolean {
if (!(error instanceof Error)) {
return false;
}
const message = error.message.toLowerCase();
return (
message.includes("kimi api error (401)") &&
(message.includes("incorrect api key") || message.includes("incorrect_api_key"))
);
}
describeLive("moonshot plugin live", () => {
it("runs Kimi web search through the provider tool", async () => {
const provider = createKimiWebSearchProvider();
@@ -40,6 +51,10 @@ describeLive("moonshot plugin live", () => {
break;
} catch (error) {
lastError = error;
if (isKimiAuthDrift(error)) {
console.warn("[moonshot:live] skip Kimi web search: auth drift");
return;
}
if (!isTransientKimiSearchError(error) || attempt === 1) {
throw error;
}

View File

@@ -5,7 +5,7 @@ import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { applyExtraParamsToAgent } from "./embedded-agent-runner.js";
import { isLiveTestEnabled } from "./live-test-helpers.js";
import { isLiveBillingDrift } from "./live-test-provider-drift.js";
import { isLiveAuthDrift, isLiveBillingDrift } from "./live-test-provider-drift.js";
const OPENAI_KEY = process.env.OPENAI_API_KEY ?? "";
const ANTHROPIC_KEY = process.env.ANTHROPIC_API_KEY ?? "";
@@ -143,9 +143,15 @@ describeAnthropicLive("embedded agent extra params (anthropic live)", () => {
usage?: { service_tier?: string };
};
const errorMessage = json.error?.message ?? `HTTP ${res.status}`;
if (!res.ok && isLiveBillingDrift(errorMessage)) {
console.warn(`[anthropic:live] skip service_tier ${serviceTier}: billing drift`);
return null;
if (!res.ok) {
if (isLiveBillingDrift(errorMessage)) {
console.warn(`[anthropic:live] skip service_tier ${serviceTier}: billing drift`);
return null;
}
if (isLiveAuthDrift(errorMessage)) {
console.warn(`[anthropic:live] skip service_tier ${serviceTier}: auth drift`);
return null;
}
}
expect(res.ok, errorMessage).toBe(true);
return json;

View File

@@ -18,6 +18,7 @@ describe("live test provider drift", () => {
expect(
isLiveAuthDrift('401 {"error":{"message":"The API key you provided is invalid."}}'),
).toBe(true);
expect(isLiveAuthDrift("invalid x-api-key")).toBe(true);
});
it("classifies API-key rate-limit drift", () => {

View File

@@ -47,7 +47,13 @@ export function liveProviderErrorText(error: unknown): string {
/** Returns whether an error is expected live auth/account drift. */
export function isLiveAuthDrift(error: unknown): boolean {
return isAuthErrorMessage(liveProviderErrorText(error));
const raw = liveProviderErrorText(error);
const message = normalizeLowercaseStringOrEmpty(raw);
return (
isAuthErrorMessage(raw) ||
message.includes("invalid x-api-key") ||
message.includes("incorrect x-api-key")
);
}
/** Returns whether an error is expected live billing/quota drift. */

View File

@@ -19,6 +19,7 @@ import {
isServerErrorMessage,
} from "../../plugin-sdk/test-env.js";
import { isLiveTestEnabled } from "../live-test-helpers.js";
import { isLiveAuthDrift } from "../live-test-provider-drift.js";
import { createImageTool, testing } from "./image-tool.js";
const OPENAI_API_KEY = process.env.OPENAI_API_KEY?.trim() ?? "";
@@ -115,6 +116,7 @@ function isSkippableLiveError(error: unknown): boolean {
const message = formatLiveError(error);
return (
isBillingErrorMessage(message) ||
isLiveAuthDrift(message) ||
isOverloadedErrorMessage(message) ||
isServerErrorMessage(message) ||
/timed out|operation was aborted/i.test(message)

View File

@@ -0,0 +1,15 @@
import { resolveFfmpegBin } from "openclaw/plugin-sdk/media-runtime";
export function hasTrustedFfmpegForLiveVoiceNote(label: string): boolean {
try {
resolveFfmpegBin();
return true;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (message.includes("ffmpeg not found in trusted system directories")) {
console.warn(`[${label}:live] skip voice-note transcode: ffmpeg unavailable`);
return false;
}
throw error;
}
}