ci: require codex profiles for live probes

This commit is contained in:
Peter Steinberger
2026-05-26 18:51:23 +01:00
parent cd46057b90
commit 6930538500
5 changed files with 86 additions and 15 deletions
+20 -1
View File
@@ -1,5 +1,10 @@
import { describe, expect, it } from "vitest";
import { isLiveProfileKeyModeEnabled, isLiveTestEnabled } from "./live-test-helpers.js";
import {
isLiveProfileKeyModeEnabled,
isLiveTestEnabled,
requiresLiveProfileCredential,
resolveLiveCredentialPrecedence,
} from "./live-test-helpers.js";
describe("isLiveTestEnabled", () => {
it("treats LIVE and OPENCLAW_LIVE_TEST as shared live gates", () => {
@@ -21,3 +26,17 @@ describe("isLiveProfileKeyModeEnabled", () => {
expect(isLiveProfileKeyModeEnabled({ LIVE: "1" })).toBe(false);
});
});
describe("live credential precedence", () => {
it("uses profile-first auth for Codex even when the global live mode is env-first", () => {
expect(resolveLiveCredentialPrecedence("openai-codex", false)).toBe("profile-first");
expect(requiresLiveProfileCredential("openai-codex", false)).toBe(true);
});
it("keeps env-first auth for normal providers unless profile keys are required", () => {
expect(resolveLiveCredentialPrecedence("openai", false)).toBe("env-first");
expect(resolveLiveCredentialPrecedence("openai", true)).toBe("profile-first");
expect(requiresLiveProfileCredential("openai", false)).toBe(false);
expect(requiresLiveProfileCredential("openai", true)).toBe(true);
});
});
+16
View File
@@ -16,6 +16,22 @@ export function isLiveProfileKeyModeEnabled(env: NodeJS.ProcessEnv = process.env
return isTruthyEnvValue(env.OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS);
}
export function requiresLiveProfileCredential(
provider: string,
requireProfileKeys: boolean,
): boolean {
return requireProfileKeys || provider === "openai-codex";
}
export function resolveLiveCredentialPrecedence(
provider: string,
requireProfileKeys: boolean,
): "profile-first" | "env-first" {
return requiresLiveProfileCredential(provider, requireProfileKeys)
? "profile-first"
: "env-first";
}
export function createSingleUserPromptMessage(content = LIVE_OK_PROMPT) {
return [
{
+14 -4
View File
@@ -41,7 +41,12 @@ import {
shouldSkipLiveModelImageProbe,
} from "./live-model-turn-probes.js";
import { createLiveTargetMatcher } from "./live-target-matcher.js";
import { isLiveProfileKeyModeEnabled, isLiveTestEnabled } from "./live-test-helpers.js";
import {
isLiveProfileKeyModeEnabled,
isLiveTestEnabled,
requiresLiveProfileCredential,
resolveLiveCredentialPrecedence,
} from "./live-test-helpers.js";
import {
isLiveBillingDrift,
isLiveRateLimitDrift,
@@ -60,7 +65,6 @@ import {
const LIVE = isLiveTestEnabled();
const DIRECT_ENABLED = Boolean(process.env.OPENCLAW_LIVE_MODELS?.trim());
const REQUIRE_PROFILE_KEYS = isLiveProfileKeyModeEnabled();
const LIVE_CREDENTIAL_PRECEDENCE = REQUIRE_PROFILE_KEYS ? "profile-first" : "env-first";
const LIVE_HEARTBEAT_MS = Math.max(1_000, toInt(process.env.OPENCLAW_LIVE_HEARTBEAT_MS, 30_000));
const LIVE_SETUP_TIMEOUT_MS = Math.max(
1_000,
@@ -862,9 +866,15 @@ describeLive("live models (profile keys)", () => {
const apiKeyInfo = await getApiKeyForModel({
model,
cfg,
credentialPrecedence: LIVE_CREDENTIAL_PRECEDENCE,
credentialPrecedence: resolveLiveCredentialPrecedence(
model.provider,
REQUIRE_PROFILE_KEYS,
),
});
if (REQUIRE_PROFILE_KEYS && !apiKeyInfo.source.startsWith("profile:")) {
if (
requiresLiveProfileCredential(model.provider, REQUIRE_PROFILE_KEYS) &&
!apiKeyInfo.source.startsWith("profile:")
) {
skipped.push({
model: id,
reason: `non-profile credential source: ${apiKeyInfo.source}`,
@@ -10,6 +10,8 @@ import {
isLiveProfileKeyModeEnabled,
isLiveTestEnabled,
logLiveProgress,
requiresLiveProfileCredential,
resolveLiveCredentialPrecedence,
} from "./live-test-helpers.js";
import { getApiKeyForModel, requireApiKey } from "./model-auth.js";
import { ensureOpenClawModelsJson } from "./models-config.js";
@@ -18,7 +20,6 @@ import { discoverAuthStorage, discoverModels } from "./pi-model-discovery.js";
const LIVE = isLiveTestEnabled();
const REQUIRE_PROFILE_KEYS = isLiveProfileKeyModeEnabled();
const LIVE_CREDENTIAL_PRECEDENCE = REQUIRE_PROFILE_KEYS ? "profile-first" : "env-first";
const DEFAULT_TARGET_MODEL_REF = "openai-codex/gpt-5.1-codex-mini";
const TARGET_MODEL_REF =
process.env.OPENCLAW_LIVE_OPENAI_REASONING_COMPAT_MODEL?.trim() || DEFAULT_TARGET_MODEL_REF;
@@ -116,14 +117,20 @@ describeLive("openai reasoning compat live", () => {
apiKeyInfo = await getApiKeyForModel({
model,
cfg,
credentialPrecedence: LIVE_CREDENTIAL_PRECEDENCE,
credentialPrecedence: resolveLiveCredentialPrecedence(
model.provider,
REQUIRE_PROFILE_KEYS,
),
});
} catch (error) {
logProgress(`[openai-reasoning-compat] skip (${String(error)})`);
return;
}
if (REQUIRE_PROFILE_KEYS && !apiKeyInfo.source.startsWith("profile:")) {
if (
requiresLiveProfileCredential(model.provider, REQUIRE_PROFILE_KEYS) &&
!apiKeyInfo.source.startsWith("profile:")
) {
logProgress(
`[openai-reasoning-compat] skip (non-profile credential source: ${apiKeyInfo.source})`,
);
@@ -170,14 +177,20 @@ describeLive("openai reasoning compat live", () => {
apiKeyInfo = await getApiKeyForModel({
model,
cfg,
credentialPrecedence: LIVE_CREDENTIAL_PRECEDENCE,
credentialPrecedence: resolveLiveCredentialPrecedence(
model.provider,
REQUIRE_PROFILE_KEYS,
),
});
} catch (error) {
logProgress(`[openai-reasoning-compat] skip (${String(error)})`);
return;
}
if (REQUIRE_PROFILE_KEYS && !apiKeyInfo.source.startsWith("profile:")) {
if (
requiresLiveProfileCredential(model.provider, REQUIRE_PROFILE_KEYS) &&
!apiKeyInfo.source.startsWith("profile:")
) {
logProgress(
`[openai-reasoning-compat] skip (non-profile credential source: ${apiKeyInfo.source})`,
);
+18 -5
View File
@@ -11,6 +11,8 @@ import {
isLiveProfileKeyModeEnabled,
isLiveTestEnabled,
logLiveProgress,
requiresLiveProfileCredential,
resolveLiveCredentialPrecedence,
} from "./live-test-helpers.js";
import { getApiKeyForModel, requireApiKey } from "./model-auth.js";
import { ensureOpenClawModelsJson } from "./models-config.js";
@@ -20,7 +22,6 @@ import { transformTransportMessages } from "./transport-message-transform.js";
const LIVE = isLiveTestEnabled();
const REQUIRE_PROFILE_KEYS = isLiveProfileKeyModeEnabled();
const LIVE_CREDENTIAL_PRECEDENCE = REQUIRE_PROFILE_KEYS ? "profile-first" : "env-first";
const DEFAULT_TARGET_MODEL_REFS = "openai-codex/gpt-5.5,google/gemini-3-flash-preview";
const TARGET_MODEL_REFS = parseTargetModelRefs(
process.env.OPENCLAW_LIVE_TOOL_REPLAY_REPAIR_MODELS ?? DEFAULT_TARGET_MODEL_REFS,
@@ -214,14 +215,20 @@ describeLive("tool replay repair live", () => {
apiKeyInfo = await getApiKeyForModel({
model,
cfg,
credentialPrecedence: LIVE_CREDENTIAL_PRECEDENCE,
credentialPrecedence: resolveLiveCredentialPrecedence(
model.provider,
REQUIRE_PROFILE_KEYS,
),
});
} catch (error) {
logProgress(`[tool-replay-repair] skip ${target.ref} (${String(error)})`);
return;
}
if (REQUIRE_PROFILE_KEYS && !apiKeyInfo.source.startsWith("profile:")) {
if (
requiresLiveProfileCredential(model.provider, REQUIRE_PROFILE_KEYS) &&
!apiKeyInfo.source.startsWith("profile:")
) {
logProgress(
`[tool-replay-repair] skip ${target.ref} (non-profile credential source: ${apiKeyInfo.source})`,
);
@@ -319,14 +326,20 @@ describeLive("tool replay repair live", () => {
apiKeyInfo = await getApiKeyForModel({
model,
cfg,
credentialPrecedence: LIVE_CREDENTIAL_PRECEDENCE,
credentialPrecedence: resolveLiveCredentialPrecedence(
model.provider,
REQUIRE_PROFILE_KEYS,
),
});
} catch (error) {
logProgress(`[tool-replay-repair] skip ${target.ref} (${String(error)})`);
return;
}
if (REQUIRE_PROFILE_KEYS && !apiKeyInfo.source.startsWith("profile:")) {
if (
requiresLiveProfileCredential(model.provider, REQUIRE_PROFILE_KEYS) &&
!apiKeyInfo.source.startsWith("profile:")
) {
logProgress(
`[tool-replay-repair] skip ${target.ref} (non-profile credential source: ${apiKeyInfo.source})`,
);