mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-01 10:11:27 +02:00
add default providers and support overrides
This commit is contained in:
@@ -1554,7 +1554,17 @@ export const aiProviders = pgTable("aiProviders", {
|
|||||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||||
name: varchar("name").notNull(),
|
name: varchar("name").notNull(),
|
||||||
type: varchar("type")
|
type: varchar("type")
|
||||||
.$type<"openai" | "anthropic" | "bedrock" | "custom">()
|
.$type<
|
||||||
|
| "openai"
|
||||||
|
| "anthropic"
|
||||||
|
| "googleGemini"
|
||||||
|
| "vertexAi"
|
||||||
|
| "bedrock"
|
||||||
|
| "microsoftFoundry"
|
||||||
|
| "openRouter"
|
||||||
|
| "vercelAiGateway"
|
||||||
|
| "custom"
|
||||||
|
>()
|
||||||
.notNull(),
|
.notNull(),
|
||||||
upstreamUrl: text("upstreamUrl"),
|
upstreamUrl: text("upstreamUrl"),
|
||||||
apiKey: text("apiKey"),
|
apiKey: text("apiKey"),
|
||||||
|
|||||||
@@ -1545,7 +1545,17 @@ export const aiProviders = sqliteTable("aiProviders", {
|
|||||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
type: text("type")
|
type: text("type")
|
||||||
.$type<"openai" | "anthropic" | "bedrock" | "custom">()
|
.$type<
|
||||||
|
| "openai"
|
||||||
|
| "anthropic"
|
||||||
|
| "googleGemini"
|
||||||
|
| "vertexAi"
|
||||||
|
| "bedrock"
|
||||||
|
| "microsoftFoundry"
|
||||||
|
| "openRouter"
|
||||||
|
| "vercelAiGateway"
|
||||||
|
| "custom"
|
||||||
|
>()
|
||||||
.notNull(),
|
.notNull(),
|
||||||
upstreamUrl: text("upstreamUrl"),
|
upstreamUrl: text("upstreamUrl"),
|
||||||
apiKey: text("apiKey"),
|
apiKey: text("apiKey"),
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
export type AiProviderType = "openai" | "anthropic" | "bedrock" | "custom";
|
export type AiProviderType =
|
||||||
|
| "openai"
|
||||||
|
| "anthropic"
|
||||||
|
| "googleGemini"
|
||||||
|
| "vertexAi"
|
||||||
|
| "bedrock"
|
||||||
|
| "microsoftFoundry"
|
||||||
|
| "openRouter"
|
||||||
|
| "vercelAiGateway"
|
||||||
|
| "custom";
|
||||||
|
|
||||||
export type AiProviderAuthType = "bearer";
|
export type AiProviderAuthType = "bearer";
|
||||||
export type AiBudgetUnit = "usd" | "tokens";
|
export type AiBudgetUnit = "usd" | "tokens";
|
||||||
|
|
||||||
type AiProviderDefaults = {
|
type AiProviderDefaults = {
|
||||||
upstreamUrl: string;
|
upstreamUrl: string | null;
|
||||||
authType: AiProviderAuthType;
|
authType: AiProviderAuthType;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -19,12 +29,39 @@ export const AI_PROVIDER_DEFAULTS: Record<
|
|||||||
upstreamUrl: "https://api.anthropic.com",
|
upstreamUrl: "https://api.anthropic.com",
|
||||||
authType: "bearer"
|
authType: "bearer"
|
||||||
},
|
},
|
||||||
|
googleGemini: {
|
||||||
|
upstreamUrl: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
||||||
|
authType: "bearer"
|
||||||
|
},
|
||||||
|
vertexAi: {
|
||||||
|
upstreamUrl: null,
|
||||||
|
authType: "bearer"
|
||||||
|
},
|
||||||
bedrock: {
|
bedrock: {
|
||||||
upstreamUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
|
upstreamUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
|
||||||
authType: "bearer"
|
authType: "bearer"
|
||||||
|
},
|
||||||
|
microsoftFoundry: {
|
||||||
|
upstreamUrl: null,
|
||||||
|
authType: "bearer"
|
||||||
|
},
|
||||||
|
openRouter: {
|
||||||
|
upstreamUrl: "https://openrouter.ai/api/v1",
|
||||||
|
authType: "bearer"
|
||||||
|
},
|
||||||
|
vercelAiGateway: {
|
||||||
|
upstreamUrl: "https://ai-gateway.vercel.sh/v1",
|
||||||
|
authType: "bearer"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function providerRequiresUpstreamUrl(type: AiProviderType): boolean {
|
||||||
|
if (type === "custom") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return AI_PROVIDER_DEFAULTS[type].upstreamUrl === null;
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveAiProviderConfig(input: {
|
export function resolveAiProviderConfig(input: {
|
||||||
type: AiProviderType;
|
type: AiProviderType;
|
||||||
upstreamUrl: string | null;
|
upstreamUrl: string | null;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
aiBudgetUnitSchema,
|
aiBudgetUnitSchema,
|
||||||
aiProviderTypeSchema,
|
aiProviderTypeSchema,
|
||||||
refineBudgetFields,
|
refineBudgetFields,
|
||||||
refineCustomProviderFields
|
refineProviderUpstreamFields
|
||||||
} from "@server/routers/aiProvider/validation";
|
} from "@server/routers/aiProvider/validation";
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
const paramsSchema = z.strictObject({
|
||||||
@@ -36,7 +36,7 @@ const bodySchema = z
|
|||||||
enabled: z.boolean().optional()
|
enabled: z.boolean().optional()
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
refineCustomProviderFields(data, ctx);
|
refineProviderUpstreamFields(data, ctx);
|
||||||
refineBudgetFields(data, ctx);
|
refineBudgetFields(data, ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,17 @@ import { encrypt } from "@server/lib/crypto";
|
|||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
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 type { AiProviderType } from "@server/lib/aiProviderDefaults";
|
|
||||||
import {
|
import {
|
||||||
aiAuthTypeSchema,
|
aiAuthTypeSchema,
|
||||||
aiBudgetUnitSchema,
|
aiBudgetUnitSchema,
|
||||||
|
aiProviderTypeSchema,
|
||||||
refineBudgetFields,
|
refineBudgetFields,
|
||||||
refineCustomProviderFields
|
refineProviderUpstreamFields
|
||||||
} from "@server/routers/aiProvider/validation";
|
} from "@server/routers/aiProvider/validation";
|
||||||
|
import {
|
||||||
|
providerRequiresUpstreamUrl,
|
||||||
|
type AiProviderType
|
||||||
|
} from "@server/lib/aiProviderDefaults";
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
const paramsSchema = z.strictObject({
|
||||||
orgId: z.string().nonempty(),
|
orgId: z.string().nonempty(),
|
||||||
@@ -115,26 +119,29 @@ export async function updateAiProvider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const providerType = existing.type as AiProviderType;
|
const providerType = existing.type as AiProviderType;
|
||||||
|
const nextUpstreamUrl =
|
||||||
|
body.upstreamUrl !== undefined
|
||||||
|
? body.upstreamUrl
|
||||||
|
: existing.upstreamUrl;
|
||||||
|
const nextAuthType =
|
||||||
|
body.authType !== undefined ? body.authType : existing.authType;
|
||||||
|
|
||||||
if (providerType === "custom") {
|
if (
|
||||||
const nextUpstreamUrl =
|
providerRequiresUpstreamUrl(providerType) ||
|
||||||
body.upstreamUrl !== undefined
|
body.upstreamUrl !== undefined ||
|
||||||
? body.upstreamUrl
|
body.authType !== undefined
|
||||||
: existing.upstreamUrl;
|
) {
|
||||||
const nextAuthType =
|
|
||||||
body.authType !== undefined ? body.authType : existing.authType;
|
|
||||||
|
|
||||||
const validation = z
|
const validation = z
|
||||||
.object({
|
.object({
|
||||||
type: z.literal("custom"),
|
type: aiProviderTypeSchema,
|
||||||
upstreamUrl: z.string().nullable().optional(),
|
upstreamUrl: z.string().nullable().optional(),
|
||||||
authType: aiAuthTypeSchema.nullable().optional()
|
authType: aiAuthTypeSchema.nullable().optional()
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) =>
|
.superRefine((data, ctx) =>
|
||||||
refineCustomProviderFields(data, ctx)
|
refineProviderUpstreamFields(data, ctx)
|
||||||
)
|
)
|
||||||
.safeParse({
|
.safeParse({
|
||||||
type: "custom",
|
type: providerType,
|
||||||
upstreamUrl: nextUpstreamUrl,
|
upstreamUrl: nextUpstreamUrl,
|
||||||
authType: nextAuthType
|
authType: nextAuthType
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import type {
|
import {
|
||||||
AiBudgetUnit,
|
providerRequiresUpstreamUrl,
|
||||||
AiProviderType
|
type AiBudgetUnit,
|
||||||
|
type AiProviderType
|
||||||
} from "@server/lib/aiProviderDefaults";
|
} from "@server/lib/aiProviderDefaults";
|
||||||
|
|
||||||
export const aiProviderTypeSchema = z.enum([
|
export const aiProviderTypeSchema = z.enum([
|
||||||
"openai",
|
"openai",
|
||||||
"anthropic",
|
"anthropic",
|
||||||
|
"googleGemini",
|
||||||
|
"vertexAi",
|
||||||
"bedrock",
|
"bedrock",
|
||||||
|
"microsoftFoundry",
|
||||||
|
"openRouter",
|
||||||
|
"vercelAiGateway",
|
||||||
"custom"
|
"custom"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -36,7 +42,7 @@ export function refineBudgetFields(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refineCustomProviderFields(
|
export function refineProviderUpstreamFields(
|
||||||
data: {
|
data: {
|
||||||
type: AiProviderType;
|
type: AiProviderType;
|
||||||
upstreamUrl?: string | null;
|
upstreamUrl?: string | null;
|
||||||
@@ -44,19 +50,15 @@ export function refineCustomProviderFields(
|
|||||||
},
|
},
|
||||||
ctx: z.RefinementCtx
|
ctx: z.RefinementCtx
|
||||||
) {
|
) {
|
||||||
if (data.type !== "custom") {
|
if (providerRequiresUpstreamUrl(data.type) && !data.upstreamUrl) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data.upstreamUrl) {
|
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
code: "custom",
|
code: "custom",
|
||||||
message: "upstreamUrl is required for custom providers",
|
message: `upstreamUrl is required for ${data.type} providers`,
|
||||||
path: ["upstreamUrl"]
|
path: ["upstreamUrl"]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data.authType) {
|
if (data.type === "custom" && !data.authType) {
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
code: "custom",
|
code: "custom",
|
||||||
message: "authType is required for custom providers",
|
message: "authType is required for custom providers",
|
||||||
|
|||||||
Reference in New Issue
Block a user