mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 10:02:53 +00:00
fix(scripts): clean Z.AI fallback repro temp state
This commit is contained in:
@@ -35,6 +35,22 @@ type ResolvePnpmCommandOptions = {
|
||||
};
|
||||
|
||||
const COMMAND_OUTPUT_MAX_CHARS = 512 * 1024;
|
||||
type ReproLog = (message: string) => void;
|
||||
type RunCommand = typeof runCommand;
|
||||
|
||||
type RunZaiFallbackReproDeps = {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
error?: ReproLog;
|
||||
log?: ReproLog;
|
||||
mkdtemp?: typeof fs.mkdtemp;
|
||||
mkdir?: typeof fs.mkdir;
|
||||
randomUUID?: typeof randomUUID;
|
||||
readFile?: typeof fs.readFile;
|
||||
rm?: typeof fs.rm;
|
||||
runCommand?: RunCommand;
|
||||
warn?: ReproLog;
|
||||
writeFile?: typeof fs.writeFile;
|
||||
};
|
||||
|
||||
function resolveEnvValue(env: NodeJS.ProcessEnv, name: string): string | undefined {
|
||||
const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
||||
@@ -81,20 +97,20 @@ export function resolveZaiFallbackPnpmCommand(
|
||||
return command;
|
||||
}
|
||||
|
||||
function pickAnthropicEnv(): { type: "oauth" | "api"; value: string } | null {
|
||||
const oauth = process.env.ANTHROPIC_OAUTH_TOKEN?.trim();
|
||||
function pickAnthropicEnv(env: NodeJS.ProcessEnv): { type: "oauth" | "api"; value: string } | null {
|
||||
const oauth = env.ANTHROPIC_OAUTH_TOKEN?.trim();
|
||||
if (oauth) {
|
||||
return { type: "oauth", value: oauth };
|
||||
}
|
||||
const api = process.env.ANTHROPIC_API_KEY?.trim();
|
||||
const api = env.ANTHROPIC_API_KEY?.trim();
|
||||
if (api) {
|
||||
return { type: "api", value: api };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function pickZaiKey(): string | null {
|
||||
return process.env.ZAI_API_KEY?.trim() ?? process.env.Z_AI_API_KEY?.trim() ?? null;
|
||||
function pickZaiKey(env: NodeJS.ProcessEnv): string | null {
|
||||
return env.ZAI_API_KEY?.trim() ?? env.Z_AI_API_KEY?.trim() ?? null;
|
||||
}
|
||||
|
||||
async function runCommand(
|
||||
@@ -143,97 +159,116 @@ async function runCommand(
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const anthropic = pickAnthropicEnv();
|
||||
const zaiKey = pickZaiKey();
|
||||
export async function runZaiFallbackRepro(deps: RunZaiFallbackReproDeps = {}): Promise<number> {
|
||||
const env = deps.env ?? process.env;
|
||||
const log = deps.log ?? console.log;
|
||||
const warn = deps.warn ?? console.warn;
|
||||
const error = deps.error ?? console.error;
|
||||
const mkdtemp = deps.mkdtemp ?? fs.mkdtemp;
|
||||
const mkdir = deps.mkdir ?? fs.mkdir;
|
||||
const readFile = deps.readFile ?? fs.readFile;
|
||||
const rm = deps.rm ?? fs.rm;
|
||||
const writeFile = deps.writeFile ?? fs.writeFile;
|
||||
const run = deps.runCommand ?? runCommand;
|
||||
const createUuid = deps.randomUUID ?? randomUUID;
|
||||
const anthropic = pickAnthropicEnv(env);
|
||||
const zaiKey = pickZaiKey(env);
|
||||
if (!anthropic) {
|
||||
console.error("Missing ANTHROPIC_OAUTH_TOKEN or ANTHROPIC_API_KEY.");
|
||||
process.exit(1);
|
||||
error("Missing ANTHROPIC_OAUTH_TOKEN or ANTHROPIC_API_KEY.");
|
||||
return 1;
|
||||
}
|
||||
if (!zaiKey) {
|
||||
console.error("Missing ZAI_API_KEY or Z_AI_API_KEY.");
|
||||
process.exit(1);
|
||||
error("Missing ZAI_API_KEY or Z_AI_API_KEY.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const baseDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-zai-fallback-"));
|
||||
const baseDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-zai-fallback-"));
|
||||
const stateDir = path.join(baseDir, "state");
|
||||
const configPath = path.join(baseDir, "openclaw.json");
|
||||
await fs.mkdir(stateDir, { recursive: true });
|
||||
try {
|
||||
await mkdir(stateDir, { recursive: true });
|
||||
|
||||
const config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: {
|
||||
primary: "anthropic/claude-opus-4-6",
|
||||
fallbacks: ["zai/glm-4.7"],
|
||||
},
|
||||
models: {
|
||||
"anthropic/claude-opus-4-6": {},
|
||||
"anthropic/claude-opus-4-5": {},
|
||||
"zai/glm-4.7": {},
|
||||
const config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: {
|
||||
primary: "anthropic/claude-opus-4-6",
|
||||
fallbacks: ["zai/glm-4.7"],
|
||||
},
|
||||
models: {
|
||||
"anthropic/claude-opus-4-6": {},
|
||||
"anthropic/claude-opus-4-5": {},
|
||||
"zai/glm-4.7": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf8");
|
||||
};
|
||||
await writeFile(configPath, JSON.stringify(config, null, 2), "utf8");
|
||||
|
||||
const sessionId = process.env.OPENCLAW_ZAI_FALLBACK_SESSION_ID ?? randomUUID();
|
||||
const sessionId = env.OPENCLAW_ZAI_FALLBACK_SESSION_ID ?? createUuid();
|
||||
|
||||
const baseEnv: NodeJS.ProcessEnv = {
|
||||
...process.env,
|
||||
OPENCLAW_CONFIG_PATH: configPath,
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
ZAI_API_KEY: zaiKey,
|
||||
Z_AI_API_KEY: "",
|
||||
};
|
||||
const baseEnv: NodeJS.ProcessEnv = {
|
||||
...env,
|
||||
OPENCLAW_CONFIG_PATH: configPath,
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
ZAI_API_KEY: zaiKey,
|
||||
Z_AI_API_KEY: "",
|
||||
};
|
||||
|
||||
const envValidAnthropic: NodeJS.ProcessEnv = {
|
||||
...baseEnv,
|
||||
ANTHROPIC_OAUTH_TOKEN: anthropic.type === "oauth" ? anthropic.value : "",
|
||||
ANTHROPIC_API_KEY: anthropic.type === "api" ? anthropic.value : "",
|
||||
};
|
||||
const envValidAnthropic: NodeJS.ProcessEnv = {
|
||||
...baseEnv,
|
||||
ANTHROPIC_OAUTH_TOKEN: anthropic.type === "oauth" ? anthropic.value : "",
|
||||
ANTHROPIC_API_KEY: anthropic.type === "api" ? anthropic.value : "",
|
||||
};
|
||||
|
||||
const envInvalidAnthropic: NodeJS.ProcessEnv = {
|
||||
...baseEnv,
|
||||
ANTHROPIC_OAUTH_TOKEN: anthropic.type === "oauth" ? "invalid" : "",
|
||||
ANTHROPIC_API_KEY: anthropic.type === "api" ? "invalid" : "",
|
||||
};
|
||||
const envInvalidAnthropic: NodeJS.ProcessEnv = {
|
||||
...baseEnv,
|
||||
ANTHROPIC_OAUTH_TOKEN: anthropic.type === "oauth" ? "invalid" : "",
|
||||
ANTHROPIC_API_KEY: anthropic.type === "api" ? "invalid" : "",
|
||||
};
|
||||
|
||||
console.log("== Run 1: create tool history (primary only)");
|
||||
const toolPrompt =
|
||||
"Use the exec tool to create a file named zai-fallback-tool.txt with the content tool-ok. " +
|
||||
"Then use the read tool to display the file contents. Reply with just the file contents.";
|
||||
const run1 = await runCommand(
|
||||
"run1",
|
||||
["openclaw", "agent", "--local", "--session-id", sessionId, "--message", toolPrompt],
|
||||
envValidAnthropic,
|
||||
);
|
||||
if (run1.code !== 0) {
|
||||
process.exit(run1.code ?? 1);
|
||||
log("== Run 1: create tool history (primary only)");
|
||||
const toolPrompt =
|
||||
"Use the exec tool to create a file named zai-fallback-tool.txt with the content tool-ok. " +
|
||||
"Then use the read tool to display the file contents. Reply with just the file contents.";
|
||||
const run1 = await run(
|
||||
"run1",
|
||||
["openclaw", "agent", "--local", "--session-id", sessionId, "--message", toolPrompt],
|
||||
envValidAnthropic,
|
||||
);
|
||||
if (run1.code !== 0) {
|
||||
return run1.code ?? 1;
|
||||
}
|
||||
|
||||
const sessionFile = path.join(stateDir, "agents", "main", "sessions", `${sessionId}.jsonl`);
|
||||
const transcript = await readFile(sessionFile, "utf8").catch(() => "");
|
||||
if (!transcript.includes('"toolResult"')) {
|
||||
warn("Warning: no toolResult entries detected in session history.");
|
||||
}
|
||||
|
||||
log("== Run 2: force auth failover to Z.AI");
|
||||
const followupPrompt =
|
||||
"What is the content of zai-fallback-tool.txt? Reply with just the contents.";
|
||||
const run2 = await run(
|
||||
"run2",
|
||||
["openclaw", "agent", "--local", "--session-id", sessionId, "--message", followupPrompt],
|
||||
envInvalidAnthropic,
|
||||
);
|
||||
|
||||
if (run2.code === 0) {
|
||||
log("PASS: fallback succeeded.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
error("FAIL: fallback failed.");
|
||||
return run2.code ?? 1;
|
||||
} finally {
|
||||
await rm(baseDir, { force: true, recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
const sessionFile = path.join(stateDir, "agents", "main", "sessions", `${sessionId}.jsonl`);
|
||||
const transcript = await fs.readFile(sessionFile, "utf8").catch(() => "");
|
||||
if (!transcript.includes('"toolResult"')) {
|
||||
console.warn("Warning: no toolResult entries detected in session history.");
|
||||
}
|
||||
|
||||
console.log("== Run 2: force auth failover to Z.AI");
|
||||
const followupPrompt =
|
||||
"What is the content of zai-fallback-tool.txt? Reply with just the contents.";
|
||||
const run2 = await runCommand(
|
||||
"run2",
|
||||
["openclaw", "agent", "--local", "--session-id", sessionId, "--message", followupPrompt],
|
||||
envInvalidAnthropic,
|
||||
);
|
||||
|
||||
if (run2.code === 0) {
|
||||
console.log("PASS: fallback succeeded.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.error("FAIL: fallback failed.");
|
||||
process.exit(run2.code ?? 1);
|
||||
async function main() {
|
||||
process.exitCode = await runZaiFallbackRepro();
|
||||
}
|
||||
|
||||
function isCliEntrypoint() {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
appendBoundedReproOutput,
|
||||
runZaiFallbackRepro,
|
||||
resolveZaiFallbackPnpmCommand,
|
||||
} from "../../scripts/zai-fallback-repro.ts";
|
||||
|
||||
@@ -32,4 +36,47 @@ describe("zai fallback repro command resolution", () => {
|
||||
expect(first).toEqual({ text: "bcdef", truncatedChars: 1 });
|
||||
expect(second).toEqual({ text: "fghij", truncatedChars: 5 });
|
||||
});
|
||||
|
||||
it("cleans temporary repro state after fallback proof", async () => {
|
||||
const tempRoots: string[] = [];
|
||||
const calls: string[] = [];
|
||||
|
||||
const exitCode = await runZaiFallbackRepro({
|
||||
env: {
|
||||
ANTHROPIC_API_KEY: "anthropic-test-key",
|
||||
OPENCLAW_ZAI_FALLBACK_SESSION_ID: "session-test",
|
||||
PATH: process.env.PATH,
|
||||
ZAI_API_KEY: "zai-test-key",
|
||||
},
|
||||
error: () => {},
|
||||
log: () => {},
|
||||
mkdtemp: async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-zai-fallback-test-"));
|
||||
tempRoots.push(root);
|
||||
return root;
|
||||
},
|
||||
randomUUID: () => "uuid-test",
|
||||
runCommand: async (label, _args, env) => {
|
||||
calls.push(label);
|
||||
if (label === "run1") {
|
||||
const sessionFile = path.join(
|
||||
String(env.OPENCLAW_STATE_DIR),
|
||||
"agents",
|
||||
"main",
|
||||
"sessions",
|
||||
"session-test.jsonl",
|
||||
);
|
||||
await fs.mkdir(path.dirname(sessionFile), { recursive: true });
|
||||
await fs.writeFile(sessionFile, '{"toolResult":true}\n', "utf8");
|
||||
}
|
||||
return { code: 0, signal: null, stderr: "", stdout: "" };
|
||||
},
|
||||
warn: () => {},
|
||||
});
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(calls).toEqual(["run1", "run2"]);
|
||||
expect(tempRoots).toHaveLength(1);
|
||||
await expect(fs.stat(tempRoots[0])).rejects.toMatchObject({ code: "ENOENT" });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user