Merge pull request #3617 from fosrl/dev

Dev
This commit is contained in:
Milo Schwartz
2026-08-19 16:41:48 -04:00
committed by GitHub
21 changed files with 205 additions and 103 deletions
+3 -1
View File
@@ -21,7 +21,9 @@ export function createAiGatewayServer() {
aiGatewayServer.use(helmet());
aiGatewayServer.use(cors());
aiGatewayServer.use(express.json());
// AI requests can carry large payloads (long conversation history, tool
// results, embedded documents), well beyond express.json()'s 100kb default.
aiGatewayServer.use(express.json({ limit: "50mb" }));
aiGatewayServer.use(createAiGatewayRouter());
+1 -1
View File
@@ -69,7 +69,7 @@ export const orgs = pgTable("orgs", {
"settingsLogRetentionDaysAISessions"
) // where 0 = dont keep logs and -1 = keep forever and 9001 = end of the following year
.notNull()
.default(7),
.default(0),
sshCaPrivateKey: text("sshCaPrivateKey"), // Encrypted SSH CA private key (PEM format)
sshCaPublicKey: text("sshCaPublicKey"), // SSH CA public key (OpenSSH format)
isBillingOrg: boolean("isBillingOrg"),
+1 -1
View File
@@ -17,7 +17,7 @@ export type AiUsage = {
estimated: boolean;
};
function emptyUsage(): AiUsage {
export function emptyUsage(): AiUsage {
return {
promptTokens: 0,
cacheReadTokens: 0,
+5 -1
View File
@@ -443,7 +443,11 @@ export const configSchema = z
disable_config_managed_domains: z.boolean().optional(),
disable_product_help_banners: z.boolean().optional(),
disable_enterprise_features: z.boolean().optional(),
enable_acme_cert_sync: z.boolean().optional().default(true)
enable_acme_cert_sync: z.boolean().optional().default(true),
disable_private_http_placeholder: z
.boolean()
.optional()
.default(false)
})
.optional(),
acme: z
+21 -7
View File
@@ -48,7 +48,7 @@ export class PrivateConfig {
this.rawPrivateConfig = parsedPrivateConfig;
this.migrateDeprecatedAcmeConfig(privateEnvironment);
this.migrateDeprecatedConfig(privateEnvironment);
process.env.BRANDING_HIDE_AUTH_LAYOUT_FOOTER =
this.rawPrivateConfig.branding?.hide_auth_layout_footer === true
@@ -152,12 +152,12 @@ export class PrivateConfig {
return this.rawPrivateConfig;
}
// `flags.enable_acme_cert_sync` and `acme` used to live in the private
// config file. They now live in the public config file. If an operator
// still has them set in the private config and hasn't moved them over to
// the public config, pull them forward so behavior doesn't silently
// change out from under them.
private migrateDeprecatedAcmeConfig(privateEnvironment: any) {
// `flags.enable_acme_cert_sync`, `flags.disable_private_http_placeholder`,
// and `acme` used to live in the private config file. They now live in
// the public config file. If an operator still has them set in the
// private config and hasn't moved them over to the public config, pull
// them forward so behavior doesn't silently change out from under them.
private migrateDeprecatedConfig(privateEnvironment: any) {
const publicEnvironment: any = readPublicConfigFile();
const rawConfig: any = config.getRawConfig();
@@ -182,6 +182,20 @@ export class PrivateConfig {
);
rawConfig.acme = this.rawPrivateConfig.acme;
}
if (
privateEnvironment?.flags?.disable_private_http_placeholder !==
undefined &&
publicEnvironment?.flags?.disable_private_http_placeholder ===
undefined
) {
logger.warn(
"`flags.disable_private_http_placeholder` is deprecated in the private config file and has moved to the public config file. Using the value from the private config file for now, but please move it to the public config."
);
rawConfig.flags = rawConfig.flags ?? {};
rawConfig.flags.disable_private_http_placeholder =
this.rawPrivateConfig.flags.disable_private_http_placeholder;
}
}
}
+7 -4
View File
@@ -115,10 +115,13 @@ export const privateConfigSchema = z
// any value set here is migrated into the public config at
// startup by PrivateConfig (server/private/lib/config.ts).
enable_acme_cert_sync: z.boolean().optional(),
disable_private_http_placeholder: z
.boolean()
.optional()
.default(false)
// @deprecated Moved to the public config file as
// `flags.disable_private_http_placeholder`
// (server/lib/readConfigFile.ts). Kept here only so existing
// private config files keep parsing; any value set here is
// migrated into the public config at startup by PrivateConfig
// (server/private/lib/config.ts).
disable_private_http_placeholder: z.boolean().optional()
})
.optional()
.prefault({}),
@@ -329,8 +329,7 @@ export async function getTraefikConfig(
}[] = [];
if (
build == "enterprise" &&
!privateConfig.getRawPrivateConfig().flags
.disable_private_http_placeholder
!config.getRawConfig().flags?.disable_private_http_placeholder
) {
// we dont want to do this on the cloud
// Query siteResources in HTTP mode with SSL enabled and aliases - cert generation / HTTPS edge
+30 -12
View File
@@ -82,6 +82,7 @@ import {
needsStreamUsageInjection,
withStreamUsageOption,
extractResponseModel,
emptyUsage,
type AiUsage
} from "@server/lib/aiUsageExtraction";
import { streamAiGatewayResponse } from "@server/routers/aiGateway/streamAiGatewayResponse";
@@ -713,19 +714,35 @@ export function recordAiGatewayCompletion(args: {
budgets
} = args;
let usage: AiUsage | null = extractUsage(
capability,
responseText,
isStream,
headers
);
if (!usage || isUsageEmpty(usage)) {
usage = estimateUsage(JSON.stringify(requestBody ?? ""), responseText);
}
// A non-2xx status means the upstream provider rejected the request
// (bad auth, invalid request, rate limit, 5xx, etc.) before ever running
// the model - no tokens were actually billed, so don't estimate usage
// off the error body text or price/charge it. We still record a
// zeroed-out row below (rather than skipping it) so request-count
// dashboards built on aiUsageRecords keep counting every attempt.
const upstreamSucceeded = statusCode >= 200 && statusCode < 300;
const model = extractResponseModel(responseText) ?? requestedModel;
const pricing = getModelPricing(provider.type as AiProviderType, model);
const cost = calculateAiCost(pricing, usage);
let usage: AiUsage;
let model: string | undefined;
let pricing: ReturnType<typeof getModelPricing> = null;
let cost: ReturnType<typeof calculateAiCost> = null;
if (upstreamSucceeded) {
usage = extractUsage(capability, responseText, isStream, headers) ?? emptyUsage();
if (isUsageEmpty(usage)) {
usage = estimateUsage(
JSON.stringify(requestBody ?? ""),
responseText
);
}
model = extractResponseModel(responseText) ?? requestedModel;
pricing = getModelPricing(provider.type as AiProviderType, model);
cost = calculateAiCost(pricing, usage);
} else {
usage = emptyUsage();
model = requestedModel;
}
// Shared by the usage record and the session log so the two can be
// joined later to show token/cost usage alongside the transcript -
@@ -738,6 +755,7 @@ export function recordAiGatewayCompletion(args: {
providerId: provider.providerId,
providerType: provider.type,
model,
statusCode,
estimated: usage.estimated,
promptTokens: usage.promptTokens,
cacheReadTokens: usage.cacheReadTokens,