mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-15 00:39:43 +02:00
add niceId to provider
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import {
|
import {
|
||||||
|
aiProviders,
|
||||||
clients,
|
clients,
|
||||||
db,
|
db,
|
||||||
resourcePolicies,
|
resourcePolicies,
|
||||||
@@ -113,6 +114,32 @@ export async function getUniqueResourceName(orgId: string): Promise<string> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getUniqueProviderName(orgId: string): Promise<string> {
|
||||||
|
let loops = 0;
|
||||||
|
while (true) {
|
||||||
|
if (loops > 100) {
|
||||||
|
throw new Error("Could not generate a unique name");
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = generateName();
|
||||||
|
|
||||||
|
const aiProviderCount = await db
|
||||||
|
.select({
|
||||||
|
niceId: aiProviders.niceId,
|
||||||
|
orgId: aiProviders.orgId
|
||||||
|
})
|
||||||
|
.from(aiProviders)
|
||||||
|
.where(
|
||||||
|
and(eq(aiProviders.niceId, name), eq(aiProviders.orgId, orgId))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (aiProviderCount.length === 0) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
loops++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function getUniqueResourcePolicyName(
|
export async function getUniqueResourcePolicyName(
|
||||||
orgId: string
|
orgId: string
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
|||||||
@@ -1684,52 +1684,57 @@ export const statusHistory = pgTable(
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
export const aiProviders = pgTable("aiProviders", {
|
export const aiProviders = pgTable(
|
||||||
providerId: serial("providerId").primaryKey(),
|
"aiProviders",
|
||||||
orgId: varchar("orgId")
|
{
|
||||||
.notNull()
|
providerId: serial("providerId").primaryKey(),
|
||||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
orgId: varchar("orgId")
|
||||||
name: varchar("name").notNull(),
|
.notNull()
|
||||||
type: varchar("type")
|
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||||
.$type<
|
name: varchar("name").notNull(),
|
||||||
| "openai"
|
niceId: varchar("niceId").notNull(),
|
||||||
| "anthropic"
|
type: varchar("type")
|
||||||
| "googleGemini"
|
.$type<
|
||||||
| "vertexAi"
|
| "openai"
|
||||||
| "bedrock"
|
| "anthropic"
|
||||||
| "microsoftFoundry"
|
| "googleGemini"
|
||||||
| "openRouter"
|
| "vertexAi"
|
||||||
| "vercelAiGateway"
|
| "bedrock"
|
||||||
| "custom"
|
| "microsoftFoundry"
|
||||||
>()
|
| "openRouter"
|
||||||
.notNull(),
|
| "vercelAiGateway"
|
||||||
upstreamUrl: text("upstreamUrl"),
|
| "custom"
|
||||||
apiKey: text("apiKey"),
|
>()
|
||||||
apiKeyLastChars: varchar("apiKeyLastChars"),
|
.notNull(),
|
||||||
authType: varchar("authType")
|
upstreamUrl: text("upstreamUrl"),
|
||||||
.$type<
|
apiKey: text("apiKey"),
|
||||||
| "bearer"
|
apiKeyLastChars: varchar("apiKeyLastChars"),
|
||||||
| "x-api-key"
|
authType: varchar("authType")
|
||||||
| "x-goog-api-key"
|
.$type<
|
||||||
| "hec"
|
| "bearer"
|
||||||
| "cf-aig-authorization"
|
| "x-api-key"
|
||||||
| "none"
|
| "x-goog-api-key"
|
||||||
| "passthrough"
|
| "hec"
|
||||||
>()
|
| "cf-aig-authorization"
|
||||||
.notNull(),
|
| "none"
|
||||||
routingMode: varchar("routingMode")
|
| "passthrough"
|
||||||
.$type<"url" | "target">()
|
>()
|
||||||
.notNull()
|
.notNull(),
|
||||||
.default("url"),
|
routingMode: varchar("routingMode")
|
||||||
capabilities: text("capabilities").notNull().default("[]"),
|
.$type<"url" | "target">()
|
||||||
headers: text("headers"), // JSON array of { name, value }
|
.notNull()
|
||||||
skipTlsVerification: boolean("skipTlsVerification")
|
.default("url"),
|
||||||
.notNull()
|
capabilities: text("capabilities").notNull().default("[]"),
|
||||||
.default(false),
|
headers: text("headers"), // JSON array of { name, value }
|
||||||
enabled: boolean("enabled").notNull().default(true),
|
skipTlsVerification: boolean("skipTlsVerification")
|
||||||
createdAt: bigint("createdAt", { mode: "number" }).notNull(),
|
.notNull()
|
||||||
updatedAt: bigint("updatedAt", { mode: "number" }).notNull()
|
.default(false),
|
||||||
});
|
enabled: boolean("enabled").notNull().default(true),
|
||||||
|
createdAt: bigint("createdAt", { mode: "number" }).notNull(),
|
||||||
|
updatedAt: bigint("updatedAt", { mode: "number" }).notNull()
|
||||||
|
},
|
||||||
|
(t) => [index("idx_aiProviders_orgId_niceId").on(t.orgId, t.niceId)]
|
||||||
|
);
|
||||||
|
|
||||||
export const aiModels = pgTable(
|
export const aiModels = pgTable(
|
||||||
"aiModels",
|
"aiModels",
|
||||||
|
|||||||
@@ -1668,52 +1668,59 @@ export const statusHistory = sqliteTable(
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
export const aiProviders = sqliteTable("aiProviders", {
|
export const aiProviders = sqliteTable(
|
||||||
providerId: integer("providerId").primaryKey({ autoIncrement: true }),
|
"aiProviders",
|
||||||
orgId: text("orgId")
|
{
|
||||||
.notNull()
|
providerId: integer("providerId").primaryKey({ autoIncrement: true }),
|
||||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
orgId: text("orgId")
|
||||||
name: text("name").notNull(),
|
.notNull()
|
||||||
type: text("type")
|
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||||
.$type<
|
name: text("name").notNull(),
|
||||||
| "openai"
|
niceId: text("niceId").notNull(),
|
||||||
| "anthropic"
|
type: text("type")
|
||||||
| "googleGemini"
|
.$type<
|
||||||
| "vertexAi"
|
| "openai"
|
||||||
| "bedrock"
|
| "anthropic"
|
||||||
| "microsoftFoundry"
|
| "googleGemini"
|
||||||
| "openRouter"
|
| "vertexAi"
|
||||||
| "vercelAiGateway"
|
| "bedrock"
|
||||||
| "custom"
|
| "microsoftFoundry"
|
||||||
>()
|
| "openRouter"
|
||||||
.notNull(),
|
| "vercelAiGateway"
|
||||||
upstreamUrl: text("upstreamUrl"),
|
| "custom"
|
||||||
apiKey: text("apiKey"),
|
>()
|
||||||
apiKeyLastChars: text("apiKeyLastChars"),
|
.notNull(),
|
||||||
authType: text("authType")
|
upstreamUrl: text("upstreamUrl"),
|
||||||
.$type<
|
apiKey: text("apiKey"),
|
||||||
| "bearer"
|
apiKeyLastChars: text("apiKeyLastChars"),
|
||||||
| "x-api-key"
|
authType: text("authType")
|
||||||
| "x-goog-api-key"
|
.$type<
|
||||||
| "hec"
|
| "bearer"
|
||||||
| "cf-aig-authorization"
|
| "x-api-key"
|
||||||
| "none"
|
| "x-goog-api-key"
|
||||||
| "passthrough"
|
| "hec"
|
||||||
>()
|
| "cf-aig-authorization"
|
||||||
.notNull(),
|
| "none"
|
||||||
routingMode: text("routingMode")
|
| "passthrough"
|
||||||
.$type<"url" | "target">()
|
>()
|
||||||
.notNull()
|
.notNull(),
|
||||||
.default("url"),
|
routingMode: text("routingMode")
|
||||||
capabilities: text("capabilities").notNull().default("[]"),
|
.$type<"url" | "target">()
|
||||||
headers: text("headers"), // JSON array of { name, value }
|
.notNull()
|
||||||
skipTlsVerification: integer("skipTlsVerification", { mode: "boolean" })
|
.default("url"),
|
||||||
.notNull()
|
capabilities: text("capabilities").notNull().default("[]"),
|
||||||
.default(false),
|
headers: text("headers"), // JSON array of { name, value }
|
||||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
skipTlsVerification: integer("skipTlsVerification", { mode: "boolean" })
|
||||||
createdAt: integer("createdAt").notNull(),
|
.notNull()
|
||||||
updatedAt: integer("updatedAt").notNull()
|
.default(false),
|
||||||
});
|
enabled: integer("enabled", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(true),
|
||||||
|
createdAt: integer("createdAt").notNull(),
|
||||||
|
updatedAt: integer("updatedAt").notNull()
|
||||||
|
},
|
||||||
|
(t) => [index("idx_aiProviders_orgId_niceId").on(t.orgId, t.niceId)]
|
||||||
|
);
|
||||||
|
|
||||||
export const aiModels = sqliteTable(
|
export const aiModels = sqliteTable(
|
||||||
"aiModels",
|
"aiModels",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
refineProviderUpstreamFields
|
refineProviderUpstreamFields
|
||||||
} from "@server/routers/aiProvider/validation";
|
} from "@server/routers/aiProvider/validation";
|
||||||
import { serializeCapabilities } from "@server/lib/aiCapabilities";
|
import { serializeCapabilities } from "@server/lib/aiCapabilities";
|
||||||
|
import { getUniqueProviderName, getUniqueResourceName } from "@server/db/names";
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
const paramsSchema = z.strictObject({
|
||||||
orgId: z.string().nonempty()
|
orgId: z.string().nonempty()
|
||||||
@@ -133,11 +134,14 @@ export async function createAiProvider(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const niceId = await getUniqueProviderName(orgId);
|
||||||
|
|
||||||
const [provider] = await db
|
const [provider] = await db
|
||||||
.insert(aiProviders)
|
.insert(aiProviders)
|
||||||
.values({
|
.values({
|
||||||
orgId,
|
orgId,
|
||||||
name,
|
name,
|
||||||
|
niceId,
|
||||||
type,
|
type,
|
||||||
upstreamUrl: resolved.upstreamUrl,
|
upstreamUrl: resolved.upstreamUrl,
|
||||||
apiKey: encryptedApiKey,
|
apiKey: encryptedApiKey,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import createHttpError from "http-errors";
|
|||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq, ne, and } from "drizzle-orm";
|
||||||
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 type { CreateOrEditAiProviderResponse } from "@server/routers/aiProvider/types";
|
import type { CreateOrEditAiProviderResponse } from "@server/routers/aiProvider/types";
|
||||||
@@ -37,6 +37,15 @@ const paramsSchema = z.strictObject({
|
|||||||
|
|
||||||
const bodySchema = z.strictObject({
|
const bodySchema = z.strictObject({
|
||||||
name: z.string().nonempty().optional(),
|
name: z.string().nonempty().optional(),
|
||||||
|
niceId: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(255)
|
||||||
|
.regex(
|
||||||
|
/^[a-zA-Z0-9-]+$/,
|
||||||
|
"niceId can only contain letters, numbers, and dashes"
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
upstreamUrl: z.url().optional().nullable(),
|
upstreamUrl: z.url().optional().nullable(),
|
||||||
apiKey: z.string().optional(),
|
apiKey: z.string().optional(),
|
||||||
authType: aiAuthTypeSchema.optional(),
|
authType: aiAuthTypeSchema.optional(),
|
||||||
@@ -170,6 +179,9 @@ export async function updateAiProvider(
|
|||||||
if (body.name !== undefined) {
|
if (body.name !== undefined) {
|
||||||
updateData.name = body.name;
|
updateData.name = body.name;
|
||||||
}
|
}
|
||||||
|
if (body.niceId !== undefined) {
|
||||||
|
updateData.niceId = body.niceId;
|
||||||
|
}
|
||||||
if (body.skipTlsVerification !== undefined) {
|
if (body.skipTlsVerification !== undefined) {
|
||||||
updateData.skipTlsVerification = body.skipTlsVerification;
|
updateData.skipTlsVerification = body.skipTlsVerification;
|
||||||
}
|
}
|
||||||
@@ -199,6 +211,29 @@ export async function updateAiProvider(
|
|||||||
updateData.headers = serializeAiProviderHeaders(body.headers, key);
|
updateData.headers = serializeAiProviderHeaders(body.headers, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (updateData.niceId) {
|
||||||
|
const [existingAiProvider] = await db
|
||||||
|
.select()
|
||||||
|
.from(aiProviders)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(aiProviders.niceId, updateData.niceId),
|
||||||
|
eq(aiProviders.orgId, existing.orgId),
|
||||||
|
ne(aiProviders.providerId, existing.providerId) // exclude the current provider from the search
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (existingAiProvider) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.CONFLICT,
|
||||||
|
`A resource with niceId "${updateData.niceId}" already exists`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const [provider] = await db
|
const [provider] = await db
|
||||||
.update(aiProviders)
|
.update(aiProviders)
|
||||||
.set(updateData)
|
.set(updateData)
|
||||||
|
|||||||
Reference in New Issue
Block a user