diff --git a/AgentCompany-New/src/main/services/onboarding-service.ts b/AgentCompany-New/src/main/services/onboarding-service.ts
index dcd64681..cfb47d60 100644
--- a/AgentCompany-New/src/main/services/onboarding-service.ts
+++ b/AgentCompany-New/src/main/services/onboarding-service.ts
@@ -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;
diff --git a/AgentCompany-New/src/renderer/src/app/Onboarding.tsx b/AgentCompany-New/src/renderer/src/app/Onboarding.tsx
index facd9e52..ba1a2ffd 100644
--- a/AgentCompany-New/src/renderer/src/app/Onboarding.tsx
+++ b/AgentCompany-New/src/renderer/src/app/Onboarding.tsx
@@ -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 (
@@ -262,6 +268,7 @@ function ProviderStep({
providerKey: provider.key,
providerModel: provider.defaultModel ?? provider.models[0]?.id ?? "",
providerSecret: "",
+ providerBaseUrl: "",
})}
>
@@ -283,6 +290,26 @@ function ProviderStep({
buildInput={() => ({ providerKey: selected.key })}
onComplete={(secret) => onChange({ providerSecret: secret ?? "" })}
/>
+ ) : isCustom ? (
+ <>
+
+ onChange({ providerBaseUrl: event.target.value })}
+ />
+
+
+ onChange({ providerSecret: event.target.value })}
+ />
+
+ >
) : (
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;
diff --git a/AgentCompany-New/src/renderer/src/pages/settings/ProvidersPanel.tsx b/AgentCompany-New/src/renderer/src/pages/settings/ProvidersPanel.tsx
index eebe16e0..6cd94b1a 100644
--- a/AgentCompany-New/src/renderer/src/pages/settings/ProvidersPanel.tsx
+++ b/AgentCompany-New/src/renderer/src/pages/settings/ProvidersPanel.tsx
@@ -288,9 +288,32 @@ function ProviderConnectFields({
>
);
}
+ if (entry.customEndpoint) {
+ return ;
+ }
return ;
}
+function CompatFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) {
+ const [baseUrl, setBaseUrl] = useState("");
+ return (
+ <>
+ {entry.credentialHelp}
+
+ setBaseUrl(event.target.value)}
+ />
+
+
+
+ >
+ );
+}
+
function ProviderOAuthFields({
orgId,
employeeId,
diff --git a/AgentCompany-New/src/shared/domain.ts b/AgentCompany-New/src/shared/domain.ts
index 21509a94..86e48c72 100644
--- a/AgentCompany-New/src/shared/domain.ts
+++ b/AgentCompany-New/src/shared/domain.ts
@@ -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 {
diff --git a/AgentCompany-New/src/shared/provider-catalog.ts b/AgentCompany-New/src/shared/provider-catalog.ts
index 694f5bef..d5a2be3a 100644
--- a/AgentCompany-New/src/shared/provider-catalog.ts
+++ b/AgentCompany-New/src/shared/provider-catalog.ts
@@ -136,6 +136,7 @@ const RAW_CATALOG: Omit[] = [
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[] = [
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",
},
];