Show custom OpenAI/Anthropic-compatible providers in onboarding and settings
This commit is contained in:
1 parent
4d2425dde0
commit
9996b89f72
5 files changed
+60
-4
No files matched your search
@@ -85,7 +85,7 @@ export class OnboardingService {
|
||||
private resolveProvider(providerKey: string) {
|
||||
const provider = PROVIDER_CATALOG.find((entry) => entry.key === providerKey);
|
||||
if (!provider) throw new Error(`Provider ${providerKey} is unavailable.`);
|
||||
if (provider.authType === PROVIDER_AUTH_TYPE.CLOUD) {
|
||||
if (provider.authType === PROVIDER_AUTH_TYPE.CLOUD && !provider.customEndpoint) {
|
||||
throw new Error("Cloud providers need extended configuration — connect them from Settings after the company is created.");
|
||||
}
|
||||
return provider;
|
||||
|
||||
@@ -18,6 +18,7 @@ interface Draft {
|
||||
executionMode: "ai_ceo" | "ai_cofounder";
|
||||
providerKey: string;
|
||||
providerSecret: string;
|
||||
providerBaseUrl: string;
|
||||
providerModel: string;
|
||||
goalTitle: string;
|
||||
goalDescription: string;
|
||||
@@ -43,7 +44,7 @@ export function Onboarding({
|
||||
onCompleted: () => void;
|
||||
}) {
|
||||
const availableProviders = useMemo(
|
||||
() => snapshot.providerCatalog.filter((entry) => entry.authType !== PROVIDER_AUTH_TYPE.CLOUD),
|
||||
() => snapshot.providerCatalog.filter((entry) => entry.authType !== PROVIDER_AUTH_TYPE.CLOUD || Boolean(entry.customEndpoint)),
|
||||
[snapshot.providerCatalog],
|
||||
);
|
||||
const firstProvider = availableProviders[0];
|
||||
@@ -58,6 +59,7 @@ export function Onboarding({
|
||||
executionMode: "ai_ceo",
|
||||
providerKey: firstProvider.key,
|
||||
providerSecret: "",
|
||||
providerBaseUrl: "",
|
||||
providerModel: firstProvider.defaultModel ?? firstProvider.models[0]?.id ?? "",
|
||||
goalTitle: "",
|
||||
goalDescription: "",
|
||||
@@ -93,6 +95,9 @@ export function Onboarding({
|
||||
}
|
||||
setSubmitting(true);
|
||||
setError(null);
|
||||
const providerSecret = selectedProvider.customEndpoint
|
||||
? JSON.stringify({ baseUrl: draft.providerBaseUrl.trim(), secret: draft.providerSecret.trim() })
|
||||
: draft.providerSecret.trim();
|
||||
const result = await window.agentCompany.bootstrapOnboarding({
|
||||
companyName: draft.companyName.trim(),
|
||||
companyDescription: draft.companyDescription.trim(),
|
||||
@@ -104,7 +109,7 @@ export function Onboarding({
|
||||
goalTitle: draft.goalTitle.trim(),
|
||||
goalDescription: draft.goalDescription.trim(),
|
||||
providerKey: draft.providerKey,
|
||||
providerSecret: draft.providerSecret.trim(),
|
||||
providerSecret,
|
||||
providerModel: draft.providerModel.trim(),
|
||||
});
|
||||
setSubmitting(false);
|
||||
@@ -245,6 +250,7 @@ function ProviderStep({
|
||||
const selected = providers.find((entry) => entry.key === draft.providerKey) ?? providers[0];
|
||||
if (!selected) throw new Error("No provider available for onboarding.");
|
||||
const isOAuthLike = selected.authType === "oauth_pkce" || selected.authType === "account_subscription";
|
||||
const isCustom = Boolean(selected.customEndpoint);
|
||||
|
||||
return (
|
||||
<div className="form-grid">
|
||||
@@ -262,6 +268,7 @@ function ProviderStep({
|
||||
providerKey: provider.key,
|
||||
providerModel: provider.defaultModel ?? provider.models[0]?.id ?? "",
|
||||
providerSecret: "",
|
||||
providerBaseUrl: "",
|
||||
})}
|
||||
>
|
||||
<div className="row">
|
||||
@@ -283,6 +290,26 @@ function ProviderStep({
|
||||
buildInput={() => ({ providerKey: selected.key })}
|
||||
onComplete={(secret) => onChange({ providerSecret: secret ?? "" })}
|
||||
/>
|
||||
) : isCustom ? (
|
||||
<>
|
||||
<Field label="Base URL">
|
||||
<input
|
||||
className="input"
|
||||
value={draft.providerBaseUrl}
|
||||
placeholder={selected.customEndpoint === "openai" ? "https://host/v1" : "https://host"}
|
||||
onChange={(event) => onChange({ providerBaseUrl: event.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="API key">
|
||||
<input
|
||||
className="input"
|
||||
type="password"
|
||||
autoComplete="off"
|
||||
value={draft.providerSecret}
|
||||
onChange={(event) => onChange({ providerSecret: event.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
</>
|
||||
) : (
|
||||
<Field label="API key">
|
||||
<input
|
||||
@@ -434,8 +461,9 @@ function validateProvider(draft: Draft, providers: ProviderCatalogEntry[]): stri
|
||||
if (!draft.providerKey) return "Pick a provider.";
|
||||
const selected = providers.find((entry) => entry.key === draft.providerKey);
|
||||
if (!selected) return "Provider is no longer available.";
|
||||
if (selected.customEndpoint && !draft.providerBaseUrl.trim()) return "Base URL is required.";
|
||||
if (!draft.providerSecret.trim()) {
|
||||
return selected.authType === "api_key" ? "API key is required." : "Access token is required.";
|
||||
return selected.authType === "api_key" || selected.customEndpoint ? "API key is required." : "Access token is required.";
|
||||
}
|
||||
if (!draft.providerModel.trim()) return "Model is required.";
|
||||
return null;
|
||||
|
||||
@@ -288,9 +288,32 @@ function ProviderConnectFields({
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (entry.customEndpoint) {
|
||||
return <CompatFieldsForm entry={entry} provider={provider} />;
|
||||
}
|
||||
return <CloudFieldsForm entry={entry} provider={provider} />;
|
||||
}
|
||||
|
||||
function CompatFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) {
|
||||
const [baseUrl, setBaseUrl] = useState("");
|
||||
return (
|
||||
<>
|
||||
<p style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
|
||||
<Field label="Base URL">
|
||||
<input
|
||||
className="input"
|
||||
name="cloudBaseUrl"
|
||||
value={baseUrl}
|
||||
placeholder={entry.customEndpoint === "openai" ? "https://host/v1" : "https://host"}
|
||||
onChange={(event) => setBaseUrl(event.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="API key"><input className="input" name="secret" type="password" required /></Field>
|
||||
<ModelPicker entry={entry} provider={provider} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ProviderOAuthFields({
|
||||
orgId,
|
||||
employeeId,
|
||||
|
||||
@@ -292,6 +292,9 @@ export interface ProviderCatalogEntry {
|
||||
signInUrl: string | null;
|
||||
credentialHelp: string;
|
||||
models: ProviderCatalogModel[];
|
||||
/** Set for user-configured OpenAI/Anthropic-compatible endpoints: the UI
|
||||
* collects a base URL + API key + model instead of a fixed provider form. */
|
||||
customEndpoint?: "openai" | "anthropic";
|
||||
}
|
||||
|
||||
export interface SkillDefinitionRecord {
|
||||
|
||||
@@ -136,6 +136,7 @@ const RAW_CATALOG: Omit<ProviderCatalogEntry, "models">[] = [
|
||||
capabilityTags: ["custom", "openai-compatible"],
|
||||
signInUrl: null,
|
||||
credentialHelp: "Set the base URL (ending in /v1) and an API key for any OpenAI Chat Completions–compatible endpoint, then enter the model id.",
|
||||
customEndpoint: "openai",
|
||||
},
|
||||
{
|
||||
key: "custom-anthropic",
|
||||
@@ -145,6 +146,7 @@ const RAW_CATALOG: Omit<ProviderCatalogEntry, "models">[] = [
|
||||
capabilityTags: ["custom", "anthropic-compatible"],
|
||||
signInUrl: null,
|
||||
credentialHelp: "Set the base URL and an API key for any Anthropic Messages–compatible endpoint, then enter the model id.",
|
||||
customEndpoint: "anthropic",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
Reference in new issue
Block a user