mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-06 04:31:22 +02:00
add provider specific auth modes
This commit is contained in:
+5
-1
@@ -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",
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user