add default providers and support overrides

This commit is contained in:
miloschwartz
2026-07-31 15:55:10 -04:00
parent f1a2da277e
commit 1333c8e841
6 changed files with 96 additions and 30 deletions
+11 -1
View File
@@ -1554,7 +1554,17 @@ export const aiProviders = pgTable("aiProviders", {
.references(() => orgs.orgId, { onDelete: "cascade" }),
name: varchar("name").notNull(),
type: varchar("type")
.$type<"openai" | "anthropic" | "bedrock" | "custom">()
.$type<
| "openai"
| "anthropic"
| "googleGemini"
| "vertexAi"
| "bedrock"
| "microsoftFoundry"
| "openRouter"
| "vercelAiGateway"
| "custom"
>()
.notNull(),
upstreamUrl: text("upstreamUrl"),
apiKey: text("apiKey"),
+11 -1
View File
@@ -1545,7 +1545,17 @@ export const aiProviders = sqliteTable("aiProviders", {
.references(() => orgs.orgId, { onDelete: "cascade" }),
name: text("name").notNull(),
type: text("type")
.$type<"openai" | "anthropic" | "bedrock" | "custom">()
.$type<
| "openai"
| "anthropic"
| "googleGemini"
| "vertexAi"
| "bedrock"
| "microsoftFoundry"
| "openRouter"
| "vercelAiGateway"
| "custom"
>()
.notNull(),
upstreamUrl: text("upstreamUrl"),
apiKey: text("apiKey"),
+39 -2
View File
@@ -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 AiBudgetUnit = "usd" | "tokens";
type AiProviderDefaults = {
upstreamUrl: string;
upstreamUrl: string | null;
authType: AiProviderAuthType;
};
@@ -19,12 +29,39 @@ export const AI_PROVIDER_DEFAULTS: Record<
upstreamUrl: "https://api.anthropic.com",
authType: "bearer"
},
googleGemini: {
upstreamUrl: "https://generativelanguage.googleapis.com/v1beta/openai/",
authType: "bearer"
},
vertexAi: {
upstreamUrl: null,
authType: "bearer"
},
bedrock: {
upstreamUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
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: {
type: AiProviderType;
upstreamUrl: string | null;
@@ -16,7 +16,7 @@ import {
aiBudgetUnitSchema,
aiProviderTypeSchema,
refineBudgetFields,
refineCustomProviderFields
refineProviderUpstreamFields
} from "@server/routers/aiProvider/validation";
const paramsSchema = z.strictObject({
@@ -36,7 +36,7 @@ const bodySchema = z
enabled: z.boolean().optional()
})
.superRefine((data, ctx) => {
refineCustomProviderFields(data, ctx);
refineProviderUpstreamFields(data, ctx);
refineBudgetFields(data, ctx);
});
+20 -13
View File
@@ -12,13 +12,17 @@ import { encrypt } from "@server/lib/crypto";
import config from "@server/lib/config";
import type { CreateOrEditAiProviderResponse } from "@server/routers/aiProvider/types";
import { toPublicAiProvider } from "@server/routers/aiProvider/types";
import type { AiProviderType } from "@server/lib/aiProviderDefaults";
import {
aiAuthTypeSchema,
aiBudgetUnitSchema,
aiProviderTypeSchema,
refineBudgetFields,
refineCustomProviderFields
refineProviderUpstreamFields
} from "@server/routers/aiProvider/validation";
import {
providerRequiresUpstreamUrl,
type AiProviderType
} from "@server/lib/aiProviderDefaults";
const paramsSchema = z.strictObject({
orgId: z.string().nonempty(),
@@ -115,26 +119,29 @@ export async function updateAiProvider(
}
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") {
const nextUpstreamUrl =
body.upstreamUrl !== undefined
? body.upstreamUrl
: existing.upstreamUrl;
const nextAuthType =
body.authType !== undefined ? body.authType : existing.authType;
if (
providerRequiresUpstreamUrl(providerType) ||
body.upstreamUrl !== undefined ||
body.authType !== undefined
) {
const validation = z
.object({
type: z.literal("custom"),
type: aiProviderTypeSchema,
upstreamUrl: z.string().nullable().optional(),
authType: aiAuthTypeSchema.nullable().optional()
})
.superRefine((data, ctx) =>
refineCustomProviderFields(data, ctx)
refineProviderUpstreamFields(data, ctx)
)
.safeParse({
type: "custom",
type: providerType,
upstreamUrl: nextUpstreamUrl,
authType: nextAuthType
});
+13 -11
View File
@@ -1,13 +1,19 @@
import { z } from "zod";
import type {
AiBudgetUnit,
AiProviderType
import {
providerRequiresUpstreamUrl,
type AiBudgetUnit,
type AiProviderType
} from "@server/lib/aiProviderDefaults";
export const aiProviderTypeSchema = z.enum([
"openai",
"anthropic",
"googleGemini",
"vertexAi",
"bedrock",
"microsoftFoundry",
"openRouter",
"vercelAiGateway",
"custom"
]);
@@ -36,7 +42,7 @@ export function refineBudgetFields(
}
}
export function refineCustomProviderFields(
export function refineProviderUpstreamFields(
data: {
type: AiProviderType;
upstreamUrl?: string | null;
@@ -44,19 +50,15 @@ export function refineCustomProviderFields(
},
ctx: z.RefinementCtx
) {
if (data.type !== "custom") {
return;
}
if (!data.upstreamUrl) {
if (providerRequiresUpstreamUrl(data.type) && !data.upstreamUrl) {
ctx.addIssue({
code: "custom",
message: "upstreamUrl is required for custom providers",
message: `upstreamUrl is required for ${data.type} providers`,
path: ["upstreamUrl"]
});
}
if (!data.authType) {
if (data.type === "custom" && !data.authType) {
ctx.addIssue({
code: "custom",
message: "authType is required for custom providers",