Verbindungscheck beim Provider-Einrichten (Task #10)
Add an inline "Verbindung testen" button to the Neuer/Bearbeiten provider
dialogs so users can test a connection with the currently entered values
before saving.
Backend:
- New endpoint POST /providers/test-connection that accepts an ad-hoc provider
config (apiType, baseUrl, model, optional apiToken, optional providerId) in
the request body and runs a one-shot test via the existing callProvider
logic. When apiToken is empty and providerId is given, it falls back to the
stored token of that provider (edit case). Returns { ok, message }; the token
is never returned or leaked (existing redactSecrets still applies to errors).
- Defined ProviderTestConnectionInput schema + path in openapi.yaml and ran
codegen for Zod schemas and the React client.
Frontend (artifacts/skillguard/src/pages/admin.tsx):
- Add dialog: "Verbindung testen" button (disabled until Base URL + Token set
or while testing) with loading spinner and an inline green success / red
error result box. Result resets when the dialog closes.
- Edit dialog: same inline test; empty token field falls back to the stored
token via providerId. Result resets on open/close.
- The existing per-card "Verbindung testen" button is unchanged.
Verification: typecheck passes for api-server and skillguard; curl tested the
new endpoint for success-path (fetch error surfaced), empty-token, and invalid
body (400) cases. Token not present in any response.
Deviations: none.
Replit-Task-Id: 4f77293f-468c-496a-ab05-1f10e7bf8137
This commit is contained in:
parent
434ec07885
commit
543fd96afd
9 changed files with 315 additions and 6 deletions
|
|
@ -11,6 +11,7 @@ import {
|
||||||
DeleteProviderParams,
|
DeleteProviderParams,
|
||||||
TestProviderParams,
|
TestProviderParams,
|
||||||
TestProviderResponse,
|
TestProviderResponse,
|
||||||
|
TestProviderConnectionBody,
|
||||||
} from "@workspace/api-zod";
|
} from "@workspace/api-zod";
|
||||||
import { callProvider } from "../lib/aiAnalysis";
|
import { callProvider } from "../lib/aiAnalysis";
|
||||||
|
|
||||||
|
|
@ -141,4 +142,63 @@ router.post("/providers/:id/test", async (req, res) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post("/providers/test-connection", async (req, res) => {
|
||||||
|
const parsed = TestProviderConnectionBody.safeParse(req.body);
|
||||||
|
if (!parsed.success)
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ message: "Ungültige Eingabe", details: parsed.error.issues });
|
||||||
|
const d = parsed.data;
|
||||||
|
|
||||||
|
let token: string | null =
|
||||||
|
d.apiToken && d.apiToken !== "" ? d.apiToken : null;
|
||||||
|
if (!token && d.providerId !== undefined && d.providerId !== null) {
|
||||||
|
const [existing] = await db
|
||||||
|
.select()
|
||||||
|
.from(aiProvidersTable)
|
||||||
|
.where(eq(aiProvidersTable.id, d.providerId));
|
||||||
|
if (existing?.apiToken) token = existing.apiToken;
|
||||||
|
}
|
||||||
|
if (!token) {
|
||||||
|
return res.json(
|
||||||
|
TestProviderResponse.parse({
|
||||||
|
ok: false,
|
||||||
|
message: "Kein API-Token angegeben.",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider: AiProvider = {
|
||||||
|
id: d.providerId ?? 0,
|
||||||
|
name: "",
|
||||||
|
apiType: d.apiType,
|
||||||
|
baseUrl: d.baseUrl,
|
||||||
|
model: d.model,
|
||||||
|
apiToken: token,
|
||||||
|
enabled: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const reply = await callProvider(
|
||||||
|
provider,
|
||||||
|
"Du bist ein Verbindungstest.",
|
||||||
|
'Antworte mit dem einzelnen Wort "OK".',
|
||||||
|
);
|
||||||
|
return res.json(
|
||||||
|
TestProviderResponse.parse({
|
||||||
|
ok: true,
|
||||||
|
message: `Verbindung erfolgreich. Antwort: ${reply.trim().slice(0, 80) || "(leer)"}`,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return res.json(
|
||||||
|
TestProviderResponse.parse({
|
||||||
|
ok: false,
|
||||||
|
message: err instanceof Error ? err.message : "Verbindung fehlgeschlagen.",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
useListProviders, getListProvidersQueryKey, useCreateProvider, useUpdateProvider, useDeleteProvider, useTestProvider,
|
useListProviders, getListProvidersQueryKey, useCreateProvider, useUpdateProvider, useDeleteProvider, useTestProvider, useTestProviderConnection,
|
||||||
useListPrompts, getListPromptsQueryKey, useUpdatePrompt,
|
useListPrompts, getListPromptsQueryKey, useUpdatePrompt,
|
||||||
useListRules, getListRulesQueryKey, useUpdateRule,
|
useListRules, getListRulesQueryKey, useUpdateRule,
|
||||||
AiProviderApiType, RuleUpdateSeverity
|
AiProviderApiType, RuleUpdateSeverity
|
||||||
|
|
@ -30,13 +30,18 @@ function ProviderTab() {
|
||||||
const updateProvider = useUpdateProvider();
|
const updateProvider = useUpdateProvider();
|
||||||
const deleteProvider = useDeleteProvider();
|
const deleteProvider = useDeleteProvider();
|
||||||
const testProvider = useTestProvider();
|
const testProvider = useTestProvider();
|
||||||
|
const testConnection = useTestProviderConnection();
|
||||||
|
|
||||||
const [isAddOpen, setIsAddOpen] = useState(false);
|
const [isAddOpen, setIsAddOpen] = useState(false);
|
||||||
const [addForm, setAddForm] = useState({ name: "", apiType: AiProviderApiType.openai as AiProviderApiType, baseUrl: "", model: "", apiToken: "", enabled: true });
|
const [addForm, setAddForm] = useState({ name: "", apiType: AiProviderApiType.openai as AiProviderApiType, baseUrl: "", model: "", apiToken: "", enabled: true });
|
||||||
|
const [addTesting, setAddTesting] = useState(false);
|
||||||
|
const [addTestResult, setAddTestResult] = useState<{ ok: boolean; message: string } | null>(null);
|
||||||
|
|
||||||
const [editingId, setEditingId] = useState<number | null>(null);
|
const [editingId, setEditingId] = useState<number | null>(null);
|
||||||
const [editForm, setEditForm] = useState({ name: "", apiType: AiProviderApiType.openai as AiProviderApiType, baseUrl: "", model: "", apiToken: "", enabled: true });
|
const [editForm, setEditForm] = useState({ name: "", apiType: AiProviderApiType.openai as AiProviderApiType, baseUrl: "", model: "", apiToken: "", enabled: true });
|
||||||
|
const [editTesting, setEditTesting] = useState(false);
|
||||||
|
const [editTestResult, setEditTestResult] = useState<{ ok: boolean; message: string } | null>(null);
|
||||||
|
|
||||||
const [testingId, setTestingId] = useState<number | null>(null);
|
const [testingId, setTestingId] = useState<number | null>(null);
|
||||||
|
|
||||||
const invalidate = () => queryClient.invalidateQueries({ queryKey: getListProvidersQueryKey() });
|
const invalidate = () => queryClient.invalidateQueries({ queryKey: getListProvidersQueryKey() });
|
||||||
|
|
@ -99,6 +104,37 @@ function ProviderTab() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddTest = () => {
|
||||||
|
setAddTestResult(null);
|
||||||
|
setAddTesting(true);
|
||||||
|
testConnection.mutate({ data: { apiType: addForm.apiType, baseUrl: addForm.baseUrl, model: addForm.model, apiToken: addForm.apiToken } }, {
|
||||||
|
onSuccess: (res) => {
|
||||||
|
setAddTesting(false);
|
||||||
|
setAddTestResult({ ok: res.ok, message: res.message || (res.ok ? "Der API-Aufruf war erfolgreich." : "Es gab ein Problem.") });
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
setAddTesting(false);
|
||||||
|
setAddTestResult({ ok: false, message: "Verbindungstest konnte nicht durchgeführt werden." });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditTest = () => {
|
||||||
|
if (!editingId) return;
|
||||||
|
setEditTestResult(null);
|
||||||
|
setEditTesting(true);
|
||||||
|
testConnection.mutate({ data: { apiType: editForm.apiType, baseUrl: editForm.baseUrl, model: editForm.model, apiToken: editForm.apiToken, providerId: editingId } }, {
|
||||||
|
onSuccess: (res) => {
|
||||||
|
setEditTesting(false);
|
||||||
|
setEditTestResult({ ok: res.ok, message: res.message || (res.ok ? "Der API-Aufruf war erfolgreich." : "Es gab ein Problem.") });
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
setEditTesting(false);
|
||||||
|
setEditTestResult({ ok: false, message: "Verbindungstest konnte nicht durchgeführt werden." });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const openEdit = (provider: any) => {
|
const openEdit = (provider: any) => {
|
||||||
setEditForm({
|
setEditForm({
|
||||||
name: provider.name,
|
name: provider.name,
|
||||||
|
|
@ -108,6 +144,8 @@ function ProviderTab() {
|
||||||
apiToken: "",
|
apiToken: "",
|
||||||
enabled: provider.enabled
|
enabled: provider.enabled
|
||||||
});
|
});
|
||||||
|
setEditTestResult(null);
|
||||||
|
setEditTesting(false);
|
||||||
setEditingId(provider.id);
|
setEditingId(provider.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -120,7 +158,7 @@ function ProviderTab() {
|
||||||
<h2 className="text-xl font-bold">KI-Provider</h2>
|
<h2 className="text-xl font-bold">KI-Provider</h2>
|
||||||
<p className="text-sm text-muted-foreground">Konfigurieren Sie externe LLM-Provider für die semantische Analyse.</p>
|
<p className="text-sm text-muted-foreground">Konfigurieren Sie externe LLM-Provider für die semantische Analyse.</p>
|
||||||
</div>
|
</div>
|
||||||
<Dialog open={isAddOpen} onOpenChange={setIsAddOpen}>
|
<Dialog open={isAddOpen} onOpenChange={(o) => { setIsAddOpen(o); if (!o) { setAddTestResult(null); setAddTesting(false); } }}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button className="gap-2"><Plus className="w-4 h-4"/> Provider hinzufügen</Button>
|
<Button className="gap-2"><Plus className="w-4 h-4"/> Provider hinzufügen</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
|
@ -165,8 +203,18 @@ function ProviderTab() {
|
||||||
<Label>Aktiviert</Label>
|
<Label>Aktiviert</Label>
|
||||||
<Switch checked={addForm.enabled} onCheckedChange={c => setAddForm({...addForm, enabled: c})} />
|
<Switch checked={addForm.enabled} onCheckedChange={c => setAddForm({...addForm, enabled: c})} />
|
||||||
</div>
|
</div>
|
||||||
|
{addTestResult && (
|
||||||
|
<div className={`flex items-start gap-2 rounded-md border p-3 text-sm ${addTestResult.ok ? "border-green-500/50 bg-green-500/10 text-green-700 dark:text-green-400" : "border-destructive/50 bg-destructive/10 text-destructive"}`}>
|
||||||
|
{addTestResult.ok ? <CheckCircle2 className="w-4 h-4 mt-0.5 shrink-0" /> : <XCircle className="w-4 h-4 mt-0.5 shrink-0" />}
|
||||||
|
<span className="break-words">{addTestResult.message}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter className="gap-2 sm:gap-0">
|
||||||
|
<Button type="button" variant="outline" className="mr-auto" onClick={handleAddTest} disabled={!addForm.baseUrl || !addForm.apiToken || addTesting}>
|
||||||
|
{addTesting ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : <Activity className="w-4 h-4 mr-2" />}
|
||||||
|
Verbindung testen
|
||||||
|
</Button>
|
||||||
<Button type="submit" disabled={createProvider.isPending}>Speichern</Button>
|
<Button type="submit" disabled={createProvider.isPending}>Speichern</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -252,8 +300,18 @@ function ProviderTab() {
|
||||||
<Label>API Token (leer lassen zum Beibehalten)</Label>
|
<Label>API Token (leer lassen zum Beibehalten)</Label>
|
||||||
<Input type="password" value={editForm.apiToken} onChange={e => setEditForm({...editForm, apiToken: e.target.value})} placeholder="Token beibehalten" />
|
<Input type="password" value={editForm.apiToken} onChange={e => setEditForm({...editForm, apiToken: e.target.value})} placeholder="Token beibehalten" />
|
||||||
</div>
|
</div>
|
||||||
|
{editTestResult && (
|
||||||
|
<div className={`flex items-start gap-2 rounded-md border p-3 text-sm ${editTestResult.ok ? "border-green-500/50 bg-green-500/10 text-green-700 dark:text-green-400" : "border-destructive/50 bg-destructive/10 text-destructive"}`}>
|
||||||
|
{editTestResult.ok ? <CheckCircle2 className="w-4 h-4 mt-0.5 shrink-0" /> : <XCircle className="w-4 h-4 mt-0.5 shrink-0" />}
|
||||||
|
<span className="break-words">{editTestResult.message}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter className="gap-2 sm:gap-0">
|
||||||
|
<Button type="button" variant="outline" className="mr-auto" onClick={handleEditTest} disabled={!editForm.baseUrl || editTesting}>
|
||||||
|
{editTesting ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : <Activity className="w-4 h-4 mr-2" />}
|
||||||
|
Verbindung testen
|
||||||
|
</Button>
|
||||||
<Button type="submit" disabled={updateProvider.isPending}>Speichern</Button>
|
<Button type="submit" disabled={updateProvider.isPending}>Speichern</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -303,6 +303,27 @@ export interface ProviderTestResult {
|
||||||
message?: string | null;
|
message?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ProviderTestConnectionInputApiType = typeof ProviderTestConnectionInputApiType[keyof typeof ProviderTestConnectionInputApiType];
|
||||||
|
|
||||||
|
|
||||||
|
export const ProviderTestConnectionInputApiType = {
|
||||||
|
openai: 'openai',
|
||||||
|
anthropic: 'anthropic',
|
||||||
|
custom: 'custom',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export interface ProviderTestConnectionInput {
|
||||||
|
apiType: ProviderTestConnectionInputApiType;
|
||||||
|
/** @minLength 1 */
|
||||||
|
baseUrl: string;
|
||||||
|
/** @minLength 1 */
|
||||||
|
model: string;
|
||||||
|
/** Token to use for the test; omit or leave empty to fall back to the stored token of providerId */
|
||||||
|
apiToken?: string;
|
||||||
|
/** When apiToken is empty, fall back to this saved provider's stored token */
|
||||||
|
providerId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Prompt {
|
export interface Prompt {
|
||||||
id: number;
|
id: number;
|
||||||
key: string;
|
key: string;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import type {
|
||||||
HealthStatus,
|
HealthStatus,
|
||||||
Prompt,
|
Prompt,
|
||||||
PromptUpdate,
|
PromptUpdate,
|
||||||
|
ProviderTestConnectionInput,
|
||||||
ProviderTestResult,
|
ProviderTestResult,
|
||||||
Rule,
|
Rule,
|
||||||
RuleUpdate,
|
RuleUpdate,
|
||||||
|
|
@ -860,6 +861,77 @@ export const useTestProvider = <TError = ErrorType<unknown>,
|
||||||
return useMutation(getTestProviderMutationOptions(options));
|
return useMutation(getTestProviderMutationOptions(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getTestProviderConnectionUrl = () => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return `/api/providers/test-connection`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Test a provider connection with ad-hoc configuration
|
||||||
|
*/
|
||||||
|
export const testProviderConnection = async (providerTestConnectionInput: ProviderTestConnectionInput, options?: RequestInit): Promise<ProviderTestResult> => {
|
||||||
|
|
||||||
|
return customFetch<ProviderTestResult>(getTestProviderConnectionUrl(),
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', ...options?.headers },
|
||||||
|
body: JSON.stringify(
|
||||||
|
providerTestConnectionInput,)
|
||||||
|
}
|
||||||
|
);}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getTestProviderConnectionMutationOptions = <TError = ErrorType<unknown>,
|
||||||
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof testProviderConnection>>, TError,{data: BodyType<ProviderTestConnectionInput>}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||||
|
): UseMutationOptions<Awaited<ReturnType<typeof testProviderConnection>>, TError,{data: BodyType<ProviderTestConnectionInput>}, TContext> => {
|
||||||
|
|
||||||
|
const mutationKey = ['testProviderConnection'];
|
||||||
|
const {mutation: mutationOptions, request: requestOptions} = options ?
|
||||||
|
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
||||||
|
options
|
||||||
|
: {...options, mutation: {...options.mutation, mutationKey}}
|
||||||
|
: {mutation: { mutationKey, }, request: undefined};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const mutationFn: MutationFunction<Awaited<ReturnType<typeof testProviderConnection>>, {data: BodyType<ProviderTestConnectionInput>}> = (props) => {
|
||||||
|
const {data} = props ?? {};
|
||||||
|
|
||||||
|
return testProviderConnection(data,requestOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { mutationFn, ...mutationOptions }}
|
||||||
|
|
||||||
|
export type TestProviderConnectionMutationResult = NonNullable<Awaited<ReturnType<typeof testProviderConnection>>>
|
||||||
|
export type TestProviderConnectionMutationBody = BodyType<ProviderTestConnectionInput>
|
||||||
|
export type TestProviderConnectionMutationError = ErrorType<unknown>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Test a provider connection with ad-hoc configuration
|
||||||
|
*/
|
||||||
|
export const useTestProviderConnection = <TError = ErrorType<unknown>,
|
||||||
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof testProviderConnection>>, TError,{data: BodyType<ProviderTestConnectionInput>}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||||
|
): UseMutationResult<
|
||||||
|
Awaited<ReturnType<typeof testProviderConnection>>,
|
||||||
|
TError,
|
||||||
|
{data: BodyType<ProviderTestConnectionInput>},
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
return useMutation(getTestProviderConnectionMutationOptions(options));
|
||||||
|
}
|
||||||
|
|
||||||
export const getListPromptsUrl = () => {
|
export const getListPromptsUrl = () => {
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,25 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/ProviderTestResult"
|
$ref: "#/components/schemas/ProviderTestResult"
|
||||||
|
|
||||||
|
/providers/test-connection:
|
||||||
|
post:
|
||||||
|
operationId: testProviderConnection
|
||||||
|
tags: [providers]
|
||||||
|
summary: Test a provider connection with ad-hoc configuration
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ProviderTestConnectionInput"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Test result
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ProviderTestResult"
|
||||||
|
|
||||||
/prompts:
|
/prompts:
|
||||||
get:
|
get:
|
||||||
operationId: listPrompts
|
operationId: listPrompts
|
||||||
|
|
@ -584,6 +603,26 @@ components:
|
||||||
message:
|
message:
|
||||||
type: ["string", "null"]
|
type: ["string", "null"]
|
||||||
|
|
||||||
|
ProviderTestConnectionInput:
|
||||||
|
type: object
|
||||||
|
required: [apiType, baseUrl, model]
|
||||||
|
properties:
|
||||||
|
apiType:
|
||||||
|
type: string
|
||||||
|
enum: [openai, anthropic, custom]
|
||||||
|
baseUrl:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
model:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
apiToken:
|
||||||
|
type: string
|
||||||
|
description: Token to use for the test; omit or leave empty to fall back to the stored token of providerId
|
||||||
|
providerId:
|
||||||
|
type: integer
|
||||||
|
description: When apiToken is empty, fall back to this saved provider's stored token
|
||||||
|
|
||||||
Prompt:
|
Prompt:
|
||||||
type: object
|
type: object
|
||||||
required: [id, key, name, content, updatedAt]
|
required: [id, key, name, content, updatedAt]
|
||||||
|
|
|
||||||
|
|
@ -273,6 +273,27 @@ export const TestProviderResponse = zod.object({
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Test a provider connection with ad-hoc configuration
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const TestProviderConnectionBody = zod.object({
|
||||||
|
"apiType": zod.enum(['openai', 'anthropic', 'custom']),
|
||||||
|
"baseUrl": zod.string().min(1),
|
||||||
|
"model": zod.string().min(1),
|
||||||
|
"apiToken": zod.string().optional().describe('Token to use for the test; omit or leave empty to fall back to the stored token of providerId'),
|
||||||
|
"providerId": zod.number().optional().describe('When apiToken is empty, fall back to this saved provider\'s stored token')
|
||||||
|
})
|
||||||
|
|
||||||
|
export const TestProviderConnectionResponse = zod.object({
|
||||||
|
"ok": zod.boolean(),
|
||||||
|
"message": zod.string().nullish()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary List configurable AI prompts
|
* @summary List configurable AI prompts
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ export * from './findingSeverity';
|
||||||
export * from './healthStatus';
|
export * from './healthStatus';
|
||||||
export * from './prompt';
|
export * from './prompt';
|
||||||
export * from './promptUpdate';
|
export * from './promptUpdate';
|
||||||
|
export * from './providerTestConnectionInput';
|
||||||
|
export * from './providerTestConnectionInputApiType';
|
||||||
export * from './providerTestResult';
|
export * from './providerTestResult';
|
||||||
export * from './rule';
|
export * from './rule';
|
||||||
export * from './ruleAxis';
|
export * from './ruleAxis';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v8.9.1 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Api
|
||||||
|
* API specification
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { ProviderTestConnectionInputApiType } from './providerTestConnectionInputApiType';
|
||||||
|
|
||||||
|
export interface ProviderTestConnectionInput {
|
||||||
|
apiType: ProviderTestConnectionInputApiType;
|
||||||
|
/** @minLength 1 */
|
||||||
|
baseUrl: string;
|
||||||
|
/** @minLength 1 */
|
||||||
|
model: string;
|
||||||
|
/** Token to use for the test; omit or leave empty to fall back to the stored token of providerId */
|
||||||
|
apiToken?: string;
|
||||||
|
/** When apiToken is empty, fall back to this saved provider's stored token */
|
||||||
|
providerId?: number;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v8.9.1 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* Api
|
||||||
|
* API specification
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ProviderTestConnectionInputApiType = typeof ProviderTestConnectionInputApiType[keyof typeof ProviderTestConnectionInputApiType];
|
||||||
|
|
||||||
|
|
||||||
|
export const ProviderTestConnectionInputApiType = {
|
||||||
|
openai: 'openai',
|
||||||
|
anthropic: 'anthropic',
|
||||||
|
custom: 'custom',
|
||||||
|
} as const;
|
||||||
Loading…
Add table
Reference in a new issue