mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 18:38:28 +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:
165
src/commands/auth-choice.apply.huggingface.ts
Normal file
165
src/commands/auth-choice.apply.huggingface.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
||||
import {
|
||||
discoverHuggingfaceModels,
|
||||
isHuggingfacePolicyLocked,
|
||||
} from "../agents/huggingface-models.js";
|
||||
import { resolveEnvApiKey } from "../agents/model-auth.js";
|
||||
import {
|
||||
formatApiKeyPreview,
|
||||
normalizeApiKeyInput,
|
||||
validateApiKeyInput,
|
||||
} from "./auth-choice.api-key.js";
|
||||
import { applyDefaultModelChoice } from "./auth-choice.default-model.js";
|
||||
import { ensureModelAllowlistEntry } from "./model-allowlist.js";
|
||||
import {
|
||||
applyAuthProfileConfig,
|
||||
applyHuggingfaceProviderConfig,
|
||||
setHuggingfaceApiKey,
|
||||
HUGGINGFACE_DEFAULT_MODEL_REF,
|
||||
} from "./onboard-auth.js";
|
||||
|
||||
export async function applyAuthChoiceHuggingface(
|
||||
params: ApplyAuthChoiceParams,
|
||||
): Promise<ApplyAuthChoiceResult | null> {
|
||||
if (params.authChoice !== "huggingface-api-key") {
|
||||
return null;
|
||||
}
|
||||
|
||||
let nextConfig = params.config;
|
||||
let agentModelOverride: string | undefined;
|
||||
const noteAgentModel = async (model: string) => {
|
||||
if (!params.agentId) {
|
||||
return;
|
||||
}
|
||||
await params.prompter.note(
|
||||
`Default model set to ${model} for agent "${params.agentId}".`,
|
||||
"Model configured",
|
||||
);
|
||||
};
|
||||
|
||||
let hasCredential = false;
|
||||
let hfKey = "";
|
||||
|
||||
if (!hasCredential && params.opts?.token && params.opts.tokenProvider === "huggingface") {
|
||||
hfKey = normalizeApiKeyInput(params.opts.token);
|
||||
await setHuggingfaceApiKey(hfKey, params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
|
||||
if (!hasCredential) {
|
||||
await params.prompter.note(
|
||||
[
|
||||
"Hugging Face Inference Providers offer OpenAI-compatible chat completions.",
|
||||
"Create a token at: https://huggingface.co/settings/tokens (fine-grained, 'Make calls to Inference Providers').",
|
||||
].join("\n"),
|
||||
"Hugging Face",
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasCredential) {
|
||||
const envKey = resolveEnvApiKey("huggingface");
|
||||
if (envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing Hugging Face token (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
});
|
||||
if (useExisting) {
|
||||
hfKey = envKey.apiKey;
|
||||
await setHuggingfaceApiKey(hfKey, params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasCredential) {
|
||||
const key = await params.prompter.text({
|
||||
message: "Enter Hugging Face API key (HF token)",
|
||||
validate: validateApiKeyInput,
|
||||
});
|
||||
hfKey = normalizeApiKeyInput(String(key ?? ""));
|
||||
await setHuggingfaceApiKey(hfKey, params.agentDir);
|
||||
}
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId: "huggingface:default",
|
||||
provider: "huggingface",
|
||||
mode: "api_key",
|
||||
});
|
||||
|
||||
const models = await discoverHuggingfaceModels(hfKey);
|
||||
const modelRefPrefix = "huggingface/";
|
||||
const options: { value: string; label: string }[] = [];
|
||||
for (const m of models) {
|
||||
const baseRef = `${modelRefPrefix}${m.id}`;
|
||||
const label = m.name ?? m.id;
|
||||
options.push({ value: baseRef, label });
|
||||
options.push({ value: `${baseRef}:cheapest`, label: `${label} (cheapest)` });
|
||||
options.push({ value: `${baseRef}:fastest`, label: `${label} (fastest)` });
|
||||
}
|
||||
const defaultRef = HUGGINGFACE_DEFAULT_MODEL_REF;
|
||||
options.sort((a, b) => {
|
||||
if (a.value === defaultRef) {
|
||||
return -1;
|
||||
}
|
||||
if (b.value === defaultRef) {
|
||||
return 1;
|
||||
}
|
||||
return a.label.localeCompare(b.label, undefined, { sensitivity: "base" });
|
||||
});
|
||||
const selectedModelRef =
|
||||
options.length === 0
|
||||
? defaultRef
|
||||
: options.length === 1
|
||||
? options[0].value
|
||||
: await params.prompter.select({
|
||||
message: "Default Hugging Face model",
|
||||
options,
|
||||
initialValue: options.some((o) => o.value === defaultRef)
|
||||
? defaultRef
|
||||
: options[0].value,
|
||||
});
|
||||
|
||||
if (isHuggingfacePolicyLocked(selectedModelRef)) {
|
||||
await params.prompter.note(
|
||||
"Provider locked — router will choose backend by cost or speed.",
|
||||
"Hugging Face",
|
||||
);
|
||||
}
|
||||
|
||||
const applied = await applyDefaultModelChoice({
|
||||
config: nextConfig,
|
||||
setDefaultModel: params.setDefaultModel,
|
||||
defaultModel: selectedModelRef,
|
||||
applyDefaultConfig: (config) => {
|
||||
const withProvider = applyHuggingfaceProviderConfig(config);
|
||||
const existingModel = withProvider.agents?.defaults?.model;
|
||||
const withPrimary = {
|
||||
...withProvider,
|
||||
agents: {
|
||||
...withProvider.agents,
|
||||
defaults: {
|
||||
...withProvider.agents?.defaults,
|
||||
model: {
|
||||
...(existingModel && typeof existingModel === "object" && "fallbacks" in existingModel
|
||||
? {
|
||||
fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks,
|
||||
}
|
||||
: {}),
|
||||
primary: selectedModelRef,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return ensureModelAllowlistEntry({
|
||||
cfg: withPrimary,
|
||||
modelRef: selectedModelRef,
|
||||
});
|
||||
},
|
||||
applyProviderConfig: applyHuggingfaceProviderConfig,
|
||||
noteDefault: selectedModelRef,
|
||||
noteAgentModel,
|
||||
prompter: params.prompter,
|
||||
});
|
||||
nextConfig = applied.config;
|
||||
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
||||
|
||||
return { config: nextConfig, agentModelOverride };
|
||||
}
|
||||
Reference in New Issue
Block a user