mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 05:51:24 +00:00
feat(agents) : Hugging Face Inference provider first-class support and Together API fix and Direct Injection Refactor Auths [AI-assisted] (#13472)
* initial commit * removes assesment from docs * resolves automated review comments * resolves lint , type , tests , refactors , and submits * solves : why do we have to lint the tests xD * adds greptile fixes * solves a type error * solves a ci error * refactors auths * solves a failing test after i pulled from main lol * solves a failing test after i pulled from main lol * resolves token naming issue to comply with better practices when using hf / huggingface * fixes curly lints ! * fixes failing tests for google api from main * solve merge conflicts * solve failing tests with a defensive check 'undefined' openrouterapi key * fix: preserve Hugging Face auth-choice intent and token behavior (#13472) (thanks @Josephrp) * test: resolve auth-choice cherry-pick conflict cleanup (#13472) --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -34,6 +34,8 @@ describe("applyAuthChoice", () => {
|
||||
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
|
||||
const previousAnthropicKey = process.env.ANTHROPIC_API_KEY;
|
||||
const previousOpenrouterKey = process.env.OPENROUTER_API_KEY;
|
||||
const previousHfToken = process.env.HF_TOKEN;
|
||||
const previousHfHubToken = process.env.HUGGINGFACE_HUB_TOKEN;
|
||||
const previousLitellmKey = process.env.LITELLM_API_KEY;
|
||||
const previousAiGatewayKey = process.env.AI_GATEWAY_API_KEY;
|
||||
const previousCloudflareGatewayKey = process.env.CLOUDFLARE_AI_GATEWAY_API_KEY;
|
||||
@@ -73,6 +75,16 @@ describe("applyAuthChoice", () => {
|
||||
} else {
|
||||
process.env.OPENROUTER_API_KEY = previousOpenrouterKey;
|
||||
}
|
||||
if (previousHfToken === undefined) {
|
||||
delete process.env.HF_TOKEN;
|
||||
} else {
|
||||
process.env.HF_TOKEN = previousHfToken;
|
||||
}
|
||||
if (previousHfHubToken === undefined) {
|
||||
delete process.env.HUGGINGFACE_HUB_TOKEN;
|
||||
} else {
|
||||
process.env.HUGGINGFACE_HUB_TOKEN = previousHfHubToken;
|
||||
}
|
||||
if (previousLitellmKey === undefined) {
|
||||
delete process.env.LITELLM_API_KEY;
|
||||
} else {
|
||||
@@ -206,6 +218,60 @@ describe("applyAuthChoice", () => {
|
||||
expect(parsed.profiles?.["synthetic:default"]?.key).toBe("sk-synthetic-test");
|
||||
});
|
||||
|
||||
it("prompts and writes Hugging Face API key when selecting huggingface-api-key", async () => {
|
||||
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-"));
|
||||
process.env.OPENCLAW_STATE_DIR = tempStateDir;
|
||||
process.env.OPENCLAW_AGENT_DIR = path.join(tempStateDir, "agent");
|
||||
process.env.PI_CODING_AGENT_DIR = process.env.OPENCLAW_AGENT_DIR;
|
||||
|
||||
const text = vi.fn().mockResolvedValue("hf-test-token");
|
||||
const select: WizardPrompter["select"] = vi.fn(
|
||||
async (params) => params.options[0]?.value as never,
|
||||
);
|
||||
const multiselect: WizardPrompter["multiselect"] = vi.fn(async () => []);
|
||||
const prompter: WizardPrompter = {
|
||||
intro: vi.fn(noopAsync),
|
||||
outro: vi.fn(noopAsync),
|
||||
note: vi.fn(noopAsync),
|
||||
select,
|
||||
multiselect,
|
||||
text,
|
||||
confirm: vi.fn(async () => false),
|
||||
progress: vi.fn(() => ({ update: noop, stop: noop })),
|
||||
};
|
||||
const runtime: RuntimeEnv = {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn((code: number) => {
|
||||
throw new Error(`exit:${code}`);
|
||||
}),
|
||||
};
|
||||
|
||||
const result = await applyAuthChoice({
|
||||
authChoice: "huggingface-api-key",
|
||||
config: {},
|
||||
prompter,
|
||||
runtime,
|
||||
setDefaultModel: true,
|
||||
});
|
||||
|
||||
expect(text).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: expect.stringContaining("Hugging Face") }),
|
||||
);
|
||||
expect(result.config.auth?.profiles?.["huggingface:default"]).toMatchObject({
|
||||
provider: "huggingface",
|
||||
mode: "api_key",
|
||||
});
|
||||
expect(result.config.agents?.defaults?.model?.primary).toMatch(/^huggingface\/.+/);
|
||||
|
||||
const authProfilePath = authProfilePathFor(requireAgentDir());
|
||||
const raw = await fs.readFile(authProfilePath, "utf8");
|
||||
const parsed = JSON.parse(raw) as {
|
||||
profiles?: Record<string, { key?: string }>;
|
||||
};
|
||||
expect(parsed.profiles?.["huggingface:default"]?.key).toBe("hf-test-token");
|
||||
});
|
||||
|
||||
it("prompts for Z.AI endpoint when selecting zai-api-key", async () => {
|
||||
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-"));
|
||||
process.env.OPENCLAW_STATE_DIR = tempStateDir;
|
||||
@@ -301,6 +367,64 @@ describe("applyAuthChoice", () => {
|
||||
expect(result.config.models?.providers?.zai?.baseUrl).toBe(ZAI_CODING_GLOBAL_BASE_URL);
|
||||
});
|
||||
|
||||
it("maps apiKey + tokenProvider=huggingface to huggingface-api-key flow", async () => {
|
||||
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-"));
|
||||
process.env.OPENCLAW_STATE_DIR = tempStateDir;
|
||||
process.env.OPENCLAW_AGENT_DIR = path.join(tempStateDir, "agent");
|
||||
process.env.PI_CODING_AGENT_DIR = process.env.OPENCLAW_AGENT_DIR;
|
||||
delete process.env.HF_TOKEN;
|
||||
delete process.env.HUGGINGFACE_HUB_TOKEN;
|
||||
|
||||
const text = vi.fn().mockResolvedValue("should-not-be-used");
|
||||
const select: WizardPrompter["select"] = vi.fn(
|
||||
async (params) => params.options[0]?.value as never,
|
||||
);
|
||||
const multiselect: WizardPrompter["multiselect"] = vi.fn(async () => []);
|
||||
const confirm = vi.fn(async () => false);
|
||||
const prompter: WizardPrompter = {
|
||||
intro: vi.fn(noopAsync),
|
||||
outro: vi.fn(noopAsync),
|
||||
note: vi.fn(noopAsync),
|
||||
select,
|
||||
multiselect,
|
||||
text,
|
||||
confirm,
|
||||
progress: vi.fn(() => ({ update: noop, stop: noop })),
|
||||
};
|
||||
const runtime: RuntimeEnv = {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn((code: number) => {
|
||||
throw new Error(`exit:${code}`);
|
||||
}),
|
||||
};
|
||||
|
||||
const result = await applyAuthChoice({
|
||||
authChoice: "apiKey",
|
||||
config: {},
|
||||
prompter,
|
||||
runtime,
|
||||
setDefaultModel: true,
|
||||
opts: {
|
||||
tokenProvider: "huggingface",
|
||||
token: "hf-token-provider-test",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.config.auth?.profiles?.["huggingface:default"]).toMatchObject({
|
||||
provider: "huggingface",
|
||||
mode: "api_key",
|
||||
});
|
||||
expect(result.config.agents?.defaults?.model?.primary).toMatch(/^huggingface\/.+/);
|
||||
expect(text).not.toHaveBeenCalled();
|
||||
|
||||
const authProfilePath = authProfilePathFor(requireAgentDir());
|
||||
const raw = await fs.readFile(authProfilePath, "utf8");
|
||||
const parsed = JSON.parse(raw) as {
|
||||
profiles?: Record<string, { key?: string }>;
|
||||
};
|
||||
expect(parsed.profiles?.["huggingface:default"]?.key).toBe("hf-token-provider-test");
|
||||
});
|
||||
it("does not override the global default model when selecting xai-api-key without setDefaultModel", async () => {
|
||||
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-"));
|
||||
process.env.OPENCLAW_STATE_DIR = tempStateDir;
|
||||
|
||||
Reference in New Issue
Block a user