v1.2.0:7 — add SparkControl AI provider + fix base-URL footgun
SparkControl is a self-hosted local-inference gateway with an OpenAI-compatible API, reached over the internal same-box StartOS address (http://spark-control.startos:9999/v1, plain HTTP). It takes no API key, so generateOpenAIStyle gained a { requireApiKey } option and now omits the Authorization header when no key is set. The Settings form auto-detects the loaded vLLM model via SparkControl's /api/endpoints probe, mirroring the Ollama auto-detect; it's $0 in the cost UI. Custom-URL => admin-only + SSRF-guarded, same as Ollama. Also fixes a config footgun behind the empty-response report: a custom base URL could ride along to a fixed-URL provider (claude/openai/gemini) whose form field is hidden, get stored, and be silently ignored (the provider always hits its hardcoded endpoint). Both config write paths now null baseUrl for non-custom-URL providers, and the form clears it on provider change. No schema/data change (AIConfigProfile.provider is free-text). 259 tests pass; built + sideloaded to immense-voyage.local with a clean non-root launch.
This commit is contained in:
@@ -18,7 +18,14 @@ import { isCustomUrlProvider } from '@/lib/ai/providers';
|
||||
* [id]/activate/route.ts so the action is explicit + auditable.
|
||||
*/
|
||||
|
||||
const PROVIDERS = ['claude', 'openai', 'openai-compatible', 'gemini', 'ollama'] as const;
|
||||
const PROVIDERS = [
|
||||
'claude',
|
||||
'openai',
|
||||
'openai-compatible',
|
||||
'gemini',
|
||||
'ollama',
|
||||
'sparkcontrol',
|
||||
] as const;
|
||||
|
||||
export async function GET() {
|
||||
const user = await getCurrentUser();
|
||||
@@ -82,32 +89,44 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const { name, provider, model, baseUrl, apiKey, setActive } = parsed.data;
|
||||
|
||||
// Custom-URL providers (Ollama / OpenAI-compatible) are admin-only — a
|
||||
// non-admin pointing the server at an arbitrary URL is the SSRF actor
|
||||
// vector. Fixed-URL cloud providers stay per-user.
|
||||
// Custom-URL providers (Ollama, SparkControl, OpenAI-compatible) are
|
||||
// admin-only — a non-admin pointing the server at an arbitrary URL is the
|
||||
// SSRF actor vector. Fixed-URL cloud providers stay per-user.
|
||||
if (!user.isAdmin && (baseUrl || isCustomUrlProvider(provider))) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error:
|
||||
'Only an admin can configure providers with a custom base URL (Ollama / OpenAI-compatible).',
|
||||
'Only an admin can configure providers with a custom base URL (Ollama, SparkControl, OpenAI-compatible).',
|
||||
},
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
// Only custom-URL providers (Ollama / OpenAI-compatible / SparkControl) carry
|
||||
// a base URL. Fixed-URL providers (claude/openai/gemini) hit their hardcoded
|
||||
// endpoint and ignore it — so we drop any stale baseUrl here rather than
|
||||
// storing an impossible config (the footgun behind the gemini-with-a-baseURL
|
||||
// mismatch). The UI also clears the field on provider change.
|
||||
const normalizedBaseUrl = isCustomUrlProvider(provider) ? baseUrl || null : null;
|
||||
|
||||
const profile = await prisma.aIConfigProfile.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
name: name ?? defaultName(provider, model),
|
||||
provider,
|
||||
model,
|
||||
baseUrl: baseUrl || null,
|
||||
baseUrl: normalizedBaseUrl,
|
||||
apiKey: apiKey || null,
|
||||
},
|
||||
});
|
||||
|
||||
if (setActive) {
|
||||
await activate(user.id, profile.id, { provider, model, baseUrl, apiKey });
|
||||
await activate(user.id, profile.id, {
|
||||
provider,
|
||||
model,
|
||||
baseUrl: normalizedBaseUrl,
|
||||
apiKey,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
@@ -128,6 +147,7 @@ function defaultName(provider: string, model: string): string {
|
||||
'openai-compatible': 'Custom',
|
||||
gemini: 'Gemini',
|
||||
ollama: 'Ollama',
|
||||
sparkcontrol: 'SparkControl',
|
||||
};
|
||||
const label = PRETTY[provider] ?? provider;
|
||||
return `${label} · ${model}`;
|
||||
|
||||
Reference in New Issue
Block a user