add provider specific auth modes

This commit is contained in:
miloschwartz
2026-08-05 15:17:50 -04:00
parent c673dce484
commit 2e9bd50172
13 changed files with 178 additions and 78 deletions
+5 -1
View File
@@ -1652,7 +1652,7 @@
"aiProviderNetworkSettings": "Network Settings", "aiProviderNetworkSettings": "Network Settings",
"aiProviderNetworkSettingsDescription": "Choose how traffic reaches this provider", "aiProviderNetworkSettingsDescription": "Choose how traffic reaches this provider",
"aiProviderAuthSettings": "Authentication", "aiProviderAuthSettings": "Authentication",
"aiProviderAuthSettingsDescription": "Credentials used for both upstream URL and Pangolin target routing", "aiProviderAuthSettingsDescription": "Configure how this provider authenticates requests to its upstream URL",
"aiProviderType": "Provider Type", "aiProviderType": "Provider Type",
"aiProviderTypeSearch": "Search providers...", "aiProviderTypeSearch": "Search providers...",
"aiProviderTypeNotFound": "No provider type found", "aiProviderTypeNotFound": "No provider type found",
@@ -1683,6 +1683,10 @@
"aiProviderApiKeyLastChars": "API Key", "aiProviderApiKeyLastChars": "API Key",
"aiProviderAuthType": "Auth Type", "aiProviderAuthType": "Auth Type",
"aiProviderAuthTypeBearer": "Bearer", "aiProviderAuthTypeBearer": "Bearer",
"aiProviderAuthTypeXApiKey": "x-api-key",
"aiProviderAuthTypeXGoogApiKey": "x-goog-api-key",
"aiProviderAuthTypeHec": "Splunk HEC",
"aiProviderAuthTypeCfAigAuthorization": "Cloudflare AI Gateway",
"aiProviderAuthTypeDescription": "How the upstream API authenticates requests", "aiProviderAuthTypeDescription": "How the upstream API authenticates requests",
"aiProviderRoutingMode": "Routing Mode", "aiProviderRoutingMode": "Routing Mode",
"aiProviderRoutingModeDescription": "Send traffic to an upstream URL or to HTTP targets on your sites", "aiProviderRoutingModeDescription": "Send traffic to an upstream URL or to HTTP targets on your sites",
+9 -1
View File
@@ -1644,7 +1644,15 @@ export const aiProviders = pgTable("aiProviders", {
upstreamUrl: text("upstreamUrl"), upstreamUrl: text("upstreamUrl"),
apiKey: text("apiKey"), apiKey: text("apiKey"),
apiKeyLastChars: varchar("apiKeyLastChars"), apiKeyLastChars: varchar("apiKeyLastChars"),
authType: varchar("authType").$type<"bearer">(), authType: varchar("authType")
.$type<
| "bearer"
| "x-api-key"
| "x-goog-api-key"
| "hec"
| "cf-aig-authorization"
>()
.notNull(),
routingMode: varchar("routingMode") routingMode: varchar("routingMode")
.$type<"url" | "target">() .$type<"url" | "target">()
.notNull() .notNull()
+9 -1
View File
@@ -1626,7 +1626,15 @@ export const aiProviders = sqliteTable("aiProviders", {
upstreamUrl: text("upstreamUrl"), upstreamUrl: text("upstreamUrl"),
apiKey: text("apiKey"), apiKey: text("apiKey"),
apiKeyLastChars: text("apiKeyLastChars"), apiKeyLastChars: text("apiKeyLastChars"),
authType: text("authType").$type<"bearer">(), authType: text("authType")
.$type<
| "bearer"
| "x-api-key"
| "x-goog-api-key"
| "hec"
| "cf-aig-authorization"
>()
.notNull(),
routingMode: text("routingMode") routingMode: text("routingMode")
.$type<"url" | "target">() .$type<"url" | "target">()
.notNull() .notNull()
+63 -11
View File
@@ -9,7 +9,15 @@ export type AiProviderType =
| "vercelAiGateway" | "vercelAiGateway"
| "custom"; | "custom";
export type AiProviderAuthType = "bearer"; export const AI_PROVIDER_AUTH_TYPES = [
"bearer",
"x-api-key",
"x-goog-api-key",
"hec",
"cf-aig-authorization"
] as const;
export type AiProviderAuthType = (typeof AI_PROVIDER_AUTH_TYPES)[number];
export type AiBudgetUnit = "usd" | "tokens"; export type AiBudgetUnit = "usd" | "tokens";
export type AiProviderRoutingMode = "url" | "target"; export type AiProviderRoutingMode = "url" | "target";
@@ -28,11 +36,11 @@ export const AI_PROVIDER_DEFAULTS: Record<
}, },
anthropic: { anthropic: {
upstreamUrl: "https://api.anthropic.com", upstreamUrl: "https://api.anthropic.com",
authType: "bearer" authType: "x-api-key"
}, },
googleGemini: { googleGemini: {
upstreamUrl: "https://generativelanguage.googleapis.com/v1beta/openai/", upstreamUrl: "https://generativelanguage.googleapis.com/v1beta/openai/",
authType: "bearer" authType: "x-goog-api-key"
}, },
vertexAi: { vertexAi: {
upstreamUrl: null, upstreamUrl: null,
@@ -56,6 +64,13 @@ export const AI_PROVIDER_DEFAULTS: Record<
} }
}; };
const CONFLICTING_AUTH_HEADERS = [
"authorization",
"x-api-key",
"x-goog-api-key",
"cf-aig-authorization"
] as const;
export function providerRequiresUpstreamUrl( export function providerRequiresUpstreamUrl(
type: AiProviderType, type: AiProviderType,
routingMode: AiProviderRoutingMode = "url" routingMode: AiProviderRoutingMode = "url"
@@ -69,17 +84,18 @@ export function providerRequiresUpstreamUrl(
return AI_PROVIDER_DEFAULTS[type].upstreamUrl === null; return AI_PROVIDER_DEFAULTS[type].upstreamUrl === null;
} }
export function resolveAiProviderConfig(input: { export function resolveAiProviderCreateFields(input: {
type: AiProviderType; type: AiProviderType;
upstreamUrl: string | null; upstreamUrl?: string | null;
authType: AiProviderAuthType | null; authType?: AiProviderAuthType | null;
routingMode?: AiProviderRoutingMode | null; routingMode?: AiProviderRoutingMode | null;
}): { }): {
upstreamUrl: string | null; upstreamUrl: string | null;
authType: AiProviderAuthType | null; authType: AiProviderAuthType;
routingMode: AiProviderRoutingMode; routingMode: AiProviderRoutingMode;
} { } {
const routingMode = input.routingMode ?? "url"; const routingMode =
input.type === "custom" ? (input.routingMode ?? "url") : "url";
if (routingMode === "target") { if (routingMode === "target") {
return { return {
@@ -91,8 +107,8 @@ export function resolveAiProviderConfig(input: {
if (input.type === "custom") { if (input.type === "custom") {
return { return {
upstreamUrl: input.upstreamUrl, upstreamUrl: input.upstreamUrl ?? null,
authType: input.authType, authType: input.authType ?? "bearer",
routingMode routingMode
}; };
} }
@@ -100,7 +116,43 @@ export function resolveAiProviderConfig(input: {
const defaults = AI_PROVIDER_DEFAULTS[input.type]; const defaults = AI_PROVIDER_DEFAULTS[input.type];
return { return {
upstreamUrl: input.upstreamUrl ?? defaults.upstreamUrl, upstreamUrl: input.upstreamUrl ?? defaults.upstreamUrl,
authType: input.authType ?? defaults.authType, authType: defaults.authType,
routingMode routingMode
}; };
} }
/**
* Strip inbound client auth headers, then set the provider auth header
* for the given authType.
*/
export function applyAiProviderAuthHeaders(
headers: Record<string, string>,
authType: AiProviderAuthType,
apiKey: string
): void {
for (const name of CONFLICTING_AUTH_HEADERS) {
for (const key of Object.keys(headers)) {
if (key.toLowerCase() === name) {
delete headers[key];
}
}
}
switch (authType) {
case "bearer":
headers["Authorization"] = `Bearer ${apiKey}`;
break;
case "x-api-key":
headers["x-api-key"] = apiKey;
break;
case "x-goog-api-key":
headers["x-goog-api-key"] = apiKey;
break;
case "hec":
headers["Authorization"] = `Splunk ${apiKey}`;
break;
case "cf-aig-authorization":
headers["cf-aig-authorization"] = `Bearer ${apiKey}`;
break;
}
}
+4 -11
View File
@@ -19,9 +19,7 @@ import config from "@server/lib/config";
import { decrypt } from "@server/lib/crypto"; import { decrypt } from "@server/lib/crypto";
import { import {
AiProviderAuthType, AiProviderAuthType,
AiProviderRoutingMode, applyAiProviderAuthHeaders
AiProviderType,
resolveAiProviderConfig
} from "@server/lib/aiProviderDefaults"; } from "@server/lib/aiProviderDefaults";
import { import {
SESSION_COOKIE_NAME, SESSION_COOKIE_NAME,
@@ -499,12 +497,8 @@ export async function chatCompletions(
const secret = config.getRawConfig().server.secret!; const secret = config.getRawConfig().server.secret!;
const apiKey = decrypt(provider.apiKey, secret); const apiKey = decrypt(provider.apiKey, secret);
const { upstreamUrl, authType } = resolveAiProviderConfig({ const upstreamUrl = provider.upstreamUrl;
type: provider.type as AiProviderType, const authType = provider.authType as AiProviderAuthType;
upstreamUrl: provider.upstreamUrl,
authType: provider.authType as AiProviderAuthType | null,
routingMode: provider.routingMode as AiProviderRoutingMode | null
});
if (!upstreamUrl) { if (!upstreamUrl) {
return res.status(HttpCode.INTERNAL_SERVER_ERROR).json({ return res.status(HttpCode.INTERNAL_SERVER_ERROR).json({
@@ -541,8 +535,7 @@ export async function chatCompletions(
} }
headers[key] = Array.isArray(value) ? value.join(", ") : value; headers[key] = Array.isArray(value) ? value.join(", ") : value;
} }
// TODO: temporary hardcoded auth for testing; restore bearer from authType applyAiProviderAuthHeaders(headers, authType, apiKey);
headers["x-api-key"] = apiKey;
// No dedicated per-request TLS agent is wired up (no extra deps for // No dedicated per-request TLS agent is wired up (no extra deps for
// this v1 gateway) - toggle the process-wide Node TLS check instead. // this v1 gateway) - toggle the process-wide Node TLS check instead.
+11 -9
View File
@@ -9,6 +9,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi"; import { OpenAPITags, registry } from "@server/openApi";
import { encrypt } from "@server/lib/crypto"; import { encrypt } from "@server/lib/crypto";
import config from "@server/lib/config"; import config from "@server/lib/config";
import { resolveAiProviderCreateFields } from "@server/lib/aiProviderDefaults";
import type { CreateOrEditAiProviderResponse } from "@server/routers/aiProvider/types"; import type { CreateOrEditAiProviderResponse } from "@server/routers/aiProvider/types";
import { toPublicAiProvider } from "@server/routers/aiProvider/types"; import { toPublicAiProvider } from "@server/routers/aiProvider/types";
import { import {
@@ -28,7 +29,7 @@ const bodySchema = z
type: aiProviderTypeSchema, type: aiProviderTypeSchema,
upstreamUrl: z.url().optional().nullable(), upstreamUrl: z.url().optional().nullable(),
apiKey: z.string().optional(), apiKey: z.string().optional(),
authType: aiAuthTypeSchema.optional().nullable(), authType: aiAuthTypeSchema.optional(),
routingMode: aiRoutingModeSchema.optional(), routingMode: aiRoutingModeSchema.optional(),
skipTlsVerification: z.boolean().optional(), skipTlsVerification: z.boolean().optional(),
enabled: z.boolean().optional() enabled: z.boolean().optional()
@@ -101,8 +102,12 @@ export async function createAiProvider(
const encryptedApiKey = apiKey ? encrypt(apiKey, key) : null; const encryptedApiKey = apiKey ? encrypt(apiKey, key) : null;
const apiKeyLastChars = apiKey ? apiKey.slice(-4) : null; const apiKeyLastChars = apiKey ? apiKey.slice(-4) : null;
const now = Date.now(); const now = Date.now();
const resolvedRoutingMode = const resolved = resolveAiProviderCreateFields({
type === "custom" ? (routingMode ?? "url") : "url"; type,
upstreamUrl,
authType,
routingMode
});
const [provider] = await db const [provider] = await db
.insert(aiProviders) .insert(aiProviders)
@@ -110,14 +115,11 @@ export async function createAiProvider(
orgId, orgId,
name, name,
type, type,
upstreamUrl: upstreamUrl: resolved.upstreamUrl,
resolvedRoutingMode === "target"
? null
: (upstreamUrl ?? null),
apiKey: encryptedApiKey, apiKey: encryptedApiKey,
apiKeyLastChars, apiKeyLastChars,
authType: authType ?? null, authType: resolved.authType,
routingMode: resolvedRoutingMode, routingMode: resolved.routingMode,
skipTlsVerification: skipTlsVerification ?? false, skipTlsVerification: skipTlsVerification ?? false,
enabled: enabled ?? true, enabled: enabled ?? true,
createdAt: now, createdAt: now,
+4 -15
View File
@@ -1,11 +1,6 @@
import type { AiModel, AiProvider } from "@server/db"; import type { AiModel, AiProvider } from "@server/db";
import type { PaginatedResponse } from "@server/types/Pagination"; import type { PaginatedResponse } from "@server/types/Pagination";
import { import type { AiProviderAuthType } from "@server/lib/aiProviderDefaults";
resolveAiProviderConfig,
type AiProviderAuthType,
type AiProviderRoutingMode,
type AiProviderType
} from "@server/lib/aiProviderDefaults";
import { decrypt } from "@server/lib/crypto"; import { decrypt } from "@server/lib/crypto";
import config from "@server/lib/config"; import config from "@server/lib/config";
@@ -13,7 +8,7 @@ export type AiProviderPublic = Omit<AiProvider, "apiKey"> & {
/** Decrypted API key. Only included on get/create/update of a single provider. */ /** Decrypted API key. Only included on get/create/update of a single provider. */
apiKey?: string | null; apiKey?: string | null;
effectiveUpstreamUrl: string | null; effectiveUpstreamUrl: string | null;
effectiveAuthType: AiProviderAuthType | null; effectiveAuthType: AiProviderAuthType;
}; };
export type ListAiProvidersResponse = PaginatedResponse<{ export type ListAiProvidersResponse = PaginatedResponse<{
@@ -45,12 +40,6 @@ export function toPublicAiProvider(
options?: { includeApiKey?: boolean } options?: { includeApiKey?: boolean }
): AiProviderPublic { ): AiProviderPublic {
const { apiKey: encryptedApiKey, ...rest } = provider; const { apiKey: encryptedApiKey, ...rest } = provider;
const resolved = resolveAiProviderConfig({
type: provider.type as AiProviderType,
upstreamUrl: provider.upstreamUrl,
authType: provider.authType as AiProviderAuthType | null,
routingMode: provider.routingMode as AiProviderRoutingMode | null
});
let apiKey: string | null | undefined; let apiKey: string | null | undefined;
if (options?.includeApiKey) { if (options?.includeApiKey) {
@@ -67,7 +56,7 @@ export function toPublicAiProvider(
return { return {
...rest, ...rest,
...(options?.includeApiKey ? { apiKey } : {}), ...(options?.includeApiKey ? { apiKey } : {}),
effectiveUpstreamUrl: resolved.upstreamUrl, effectiveUpstreamUrl: provider.upstreamUrl,
effectiveAuthType: resolved.authType effectiveAuthType: provider.authType as AiProviderAuthType
}; };
} }
+5 -12
View File
@@ -19,6 +19,7 @@ import {
refineProviderUpstreamFields refineProviderUpstreamFields
} from "@server/routers/aiProvider/validation"; } from "@server/routers/aiProvider/validation";
import type { import type {
AiProviderAuthType,
AiProviderRoutingMode, AiProviderRoutingMode,
AiProviderType AiProviderType
} from "@server/lib/aiProviderDefaults"; } from "@server/lib/aiProviderDefaults";
@@ -31,7 +32,7 @@ const bodySchema = z.strictObject({
name: z.string().nonempty().optional(), name: z.string().nonempty().optional(),
upstreamUrl: z.url().optional().nullable(), upstreamUrl: z.url().optional().nullable(),
apiKey: z.string().optional(), apiKey: z.string().optional(),
authType: aiAuthTypeSchema.optional().nullable(), authType: aiAuthTypeSchema.optional(),
routingMode: aiRoutingModeSchema.optional(), routingMode: aiRoutingModeSchema.optional(),
skipTlsVerification: z.boolean().optional(), skipTlsVerification: z.boolean().optional(),
enabled: z.boolean().optional() enabled: z.boolean().optional()
@@ -116,17 +117,16 @@ export async function updateAiProvider(
body.upstreamUrl !== undefined body.upstreamUrl !== undefined
? body.upstreamUrl ? body.upstreamUrl
: existing.upstreamUrl; : existing.upstreamUrl;
const nextAuthType = const nextAuthType: AiProviderAuthType =
body.authType !== undefined body.authType !== undefined
? body.authType ? body.authType
: (existing.authType ?? : (existing.authType as AiProviderAuthType);
(providerType === "custom" ? "bearer" : null));
const validation = z const validation = z
.object({ .object({
type: aiProviderTypeSchema, type: aiProviderTypeSchema,
upstreamUrl: z.string().nullable().optional(), upstreamUrl: z.string().nullable().optional(),
authType: aiAuthTypeSchema.nullable().optional(), authType: aiAuthTypeSchema,
routingMode: aiRoutingModeSchema.optional() routingMode: aiRoutingModeSchema.optional()
}) })
.superRefine((data, ctx) => refineProviderUpstreamFields(data, ctx)) .superRefine((data, ctx) => refineProviderUpstreamFields(data, ctx))
@@ -167,13 +167,6 @@ export async function updateAiProvider(
} }
if (body.authType !== undefined) { if (body.authType !== undefined) {
updateData.authType = body.authType; updateData.authType = body.authType;
} else if (
providerType === "custom" &&
!existing.authType &&
nextAuthType
) {
// Backfill required authType for custom providers created without one
updateData.authType = nextAuthType;
} }
if (body.apiKey !== undefined) { if (body.apiKey !== undefined) {
+4 -2
View File
@@ -1,6 +1,8 @@
import { z } from "zod"; import { z } from "zod";
import { import {
AI_PROVIDER_AUTH_TYPES,
providerRequiresUpstreamUrl, providerRequiresUpstreamUrl,
type AiProviderAuthType,
type AiProviderRoutingMode, type AiProviderRoutingMode,
type AiProviderType type AiProviderType
} from "@server/lib/aiProviderDefaults"; } from "@server/lib/aiProviderDefaults";
@@ -17,7 +19,7 @@ export const aiProviderTypeSchema = z.enum([
"custom" "custom"
]); ]);
export const aiAuthTypeSchema = z.enum(["bearer"]); export const aiAuthTypeSchema = z.enum(AI_PROVIDER_AUTH_TYPES);
export const aiRoutingModeSchema = z.enum(["url", "target"]); export const aiRoutingModeSchema = z.enum(["url", "target"]);
@@ -25,7 +27,7 @@ export function refineProviderUpstreamFields(
data: { data: {
type: AiProviderType; type: AiProviderType;
upstreamUrl?: string | null; upstreamUrl?: string | null;
authType?: "bearer" | null; authType?: AiProviderAuthType | null;
routingMode?: AiProviderRoutingMode | null; routingMode?: AiProviderRoutingMode | null;
}, },
ctx: z.RefinementCtx ctx: z.RefinementCtx
@@ -40,7 +40,10 @@ import {
type AiProviderFormValues type AiProviderFormValues
} from "@app/lib/aiProviderFormSchema"; } from "@app/lib/aiProviderFormSchema";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import type { AiProviderType } from "@server/lib/aiProviderDefaults"; import type {
AiProviderAuthType,
AiProviderType
} from "@server/lib/aiProviderDefaults";
import type { CreateOrEditAiProviderResponse } from "@server/routers/aiProvider/types"; import type { CreateOrEditAiProviderResponse } from "@server/routers/aiProvider/types";
import type { AxiosResponse } from "axios"; import type { AxiosResponse } from "axios";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
@@ -63,7 +66,7 @@ export default function AiProviderAuthenticationPage() {
type: provider.type as AiProviderType, type: provider.type as AiProviderType,
upstreamUrl: provider.upstreamUrl ?? "", upstreamUrl: provider.upstreamUrl ?? "",
apiKey: provider.apiKey ?? "", apiKey: provider.apiKey ?? "",
authType: (provider.authType as "bearer" | null) ?? "bearer", authType: (provider.authType as AiProviderAuthType) ?? "bearer",
routingMode: (provider.routingMode as "url" | "target") ?? "url", routingMode: (provider.routingMode as "url" | "target") ?? "url",
skipTlsVerification: provider.skipTlsVerification, skipTlsVerification: provider.skipTlsVerification,
enabled: provider.enabled enabled: provider.enabled
@@ -91,7 +94,7 @@ export default function AiProviderAuthenticationPage() {
type: updated.type as AiProviderType, type: updated.type as AiProviderType,
upstreamUrl: updated.upstreamUrl ?? "", upstreamUrl: updated.upstreamUrl ?? "",
apiKey: updated.apiKey ?? "", apiKey: updated.apiKey ?? "",
authType: (updated.authType as "bearer" | null) ?? "bearer", authType: (updated.authType as AiProviderAuthType) ?? "bearer",
routingMode: (updated.routingMode as "url" | "target") ?? "url", routingMode: (updated.routingMode as "url" | "target") ?? "url",
skipTlsVerification: updated.skipTlsVerification, skipTlsVerification: updated.skipTlsVerification,
enabled: updated.enabled enabled: updated.enabled
@@ -164,6 +167,26 @@ export default function AiProviderAuthenticationPage() {
"aiProviderAuthTypeBearer" "aiProviderAuthTypeBearer"
)} )}
</SelectItem> </SelectItem>
<SelectItem value="x-api-key">
{t(
"aiProviderAuthTypeXApiKey"
)}
</SelectItem>
<SelectItem value="x-goog-api-key">
{t(
"aiProviderAuthTypeXGoogApiKey"
)}
</SelectItem>
<SelectItem value="hec">
{t(
"aiProviderAuthTypeHec"
)}
</SelectItem>
<SelectItem value="cf-aig-authorization">
{t(
"aiProviderAuthTypeCfAigAuthorization"
)}
</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
<FormDescription> <FormDescription>
@@ -45,7 +45,10 @@ import {
} from "@app/lib/aiProviderFormSchema"; } from "@app/lib/aiProviderFormSchema";
import { aiProviderQueries } from "@app/lib/queries"; import { aiProviderQueries } from "@app/lib/queries";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import type { AiProviderType } from "@server/lib/aiProviderDefaults"; import type {
AiProviderAuthType,
AiProviderType
} from "@server/lib/aiProviderDefaults";
import type { CreateOrEditAiProviderResponse } from "@server/routers/aiProvider/types"; import type { CreateOrEditAiProviderResponse } from "@server/routers/aiProvider/types";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import type { AxiosResponse } from "axios"; import type { AxiosResponse } from "axios";
@@ -72,7 +75,7 @@ export default function AiProviderNetworkPage() {
type: provider.type as AiProviderType, type: provider.type as AiProviderType,
upstreamUrl: provider.upstreamUrl ?? "", upstreamUrl: provider.upstreamUrl ?? "",
apiKey: "", apiKey: "",
authType: (provider.authType as "bearer" | null) ?? "bearer", authType: (provider.authType as AiProviderAuthType) ?? "bearer",
routingMode: (provider.routingMode as "url" | "target") ?? "url", routingMode: (provider.routingMode as "url" | "target") ?? "url",
skipTlsVerification: provider.skipTlsVerification, skipTlsVerification: provider.skipTlsVerification,
enabled: provider.enabled enabled: provider.enabled
@@ -115,7 +118,7 @@ export default function AiProviderNetworkPage() {
type: updated.type as AiProviderType, type: updated.type as AiProviderType,
upstreamUrl: updated.upstreamUrl ?? "", upstreamUrl: updated.upstreamUrl ?? "",
apiKey: "", apiKey: "",
authType: (updated.authType as "bearer" | null) ?? "bearer", authType: (updated.authType as AiProviderAuthType) ?? "bearer",
routingMode: (updated.routingMode as "url" | "target") ?? "url", routingMode: (updated.routingMode as "url" | "target") ?? "url",
skipTlsVerification: updated.skipTlsVerification, skipTlsVerification: updated.skipTlsVerification,
enabled: updated.enabled enabled: updated.enabled
@@ -527,6 +527,26 @@ export default function CreateAiProviderPage() {
"aiProviderAuthTypeBearer" "aiProviderAuthTypeBearer"
)} )}
</SelectItem> </SelectItem>
<SelectItem value="x-api-key">
{t(
"aiProviderAuthTypeXApiKey"
)}
</SelectItem>
<SelectItem value="x-goog-api-key">
{t(
"aiProviderAuthTypeXGoogApiKey"
)}
</SelectItem>
<SelectItem value="hec">
{t(
"aiProviderAuthTypeHec"
)}
</SelectItem>
<SelectItem value="cf-aig-authorization">
{t(
"aiProviderAuthTypeCfAigAuthorization"
)}
</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
<FormDescription> <FormDescription>
+12 -9
View File
@@ -1,5 +1,6 @@
import { z } from "zod"; import { z } from "zod";
import { import {
AI_PROVIDER_AUTH_TYPES,
AI_PROVIDER_DEFAULTS, AI_PROVIDER_DEFAULTS,
providerRequiresUpstreamUrl, providerRequiresUpstreamUrl,
type AiProviderType type AiProviderType
@@ -23,7 +24,7 @@ export const aiProviderFormSchema = z
type: z.enum(aiProviderTypeValues), type: z.enum(aiProviderTypeValues),
upstreamUrl: z.string().optional().nullable(), upstreamUrl: z.string().optional().nullable(),
apiKey: z.string().optional(), apiKey: z.string().optional(),
authType: z.enum(["bearer"]).optional().nullable(), authType: z.enum(AI_PROVIDER_AUTH_TYPES).optional().nullable(),
routingMode: z.enum(["url", "target"]).optional(), routingMode: z.enum(["url", "target"]).optional(),
skipTlsVerification: z.boolean().optional(), skipTlsVerification: z.boolean().optional(),
enabled: z.boolean().optional() enabled: z.boolean().optional()
@@ -138,7 +139,7 @@ export function toAiProviderCreatePayload(values: AiProviderFormValues) {
authType: authType:
values.type === "custom" values.type === "custom"
? (values.authType ?? "bearer") ? (values.authType ?? "bearer")
: (values.authType ?? undefined), : undefined,
skipTlsVerification: values.skipTlsVerification, skipTlsVerification: values.skipTlsVerification,
enabled: values.enabled ?? true enabled: values.enabled ?? true
}; };
@@ -159,14 +160,14 @@ export function toAiProviderUpdatePayload(values: AiProviderFormValues) {
name: values.name.trim(), name: values.name.trim(),
routingMode: values.type === "custom" ? routingMode : "url", routingMode: values.type === "custom" ? routingMode : "url",
upstreamUrl, upstreamUrl,
authType:
values.type === "custom"
? (values.authType ?? "bearer")
: (values.authType ?? null),
skipTlsVerification: values.skipTlsVerification ?? false, skipTlsVerification: values.skipTlsVerification ?? false,
enabled: values.enabled ?? true enabled: values.enabled ?? true
}; };
if (values.type === "custom") {
payload.authType = values.authType ?? "bearer";
}
if (values.apiKey?.trim()) { if (values.apiKey?.trim()) {
payload.apiKey = values.apiKey.trim(); payload.apiKey = values.apiKey.trim();
} }
@@ -184,11 +185,13 @@ export function toAiProviderNetworkPayload(values: AiProviderFormValues) {
} }
export function toAiProviderAuthPayload(values: AiProviderFormValues) { export function toAiProviderAuthPayload(values: AiProviderFormValues) {
const full = toAiProviderUpdatePayload(values); const payload: Record<string, unknown> = {
return {
authType: full.authType,
...(values.apiKey !== undefined ? { apiKey: values.apiKey.trim() } : {}) ...(values.apiKey !== undefined ? { apiKey: values.apiKey.trim() } : {})
}; };
if (values.type === "custom") {
payload.authType = values.authType ?? "bearer";
}
return payload;
} }
export function toAiProviderConfigurationPayload(values: AiProviderFormValues) { export function toAiProviderConfigurationPayload(values: AiProviderFormValues) {