diff --git a/assistant/src/config/core-schema.ts b/assistant/src/config/core-schema.ts index 256097d409f..30faad4c7a5 100644 --- a/assistant/src/config/core-schema.ts +++ b/assistant/src/config/core-schema.ts @@ -173,17 +173,67 @@ export const SmsConfigSchema = z.object({ .optional(), }); +export const IngressWebhookConfigSchema = z.object({ + secret: z + .string({ error: 'ingress.webhook.secret must be a string' }) + .default(''), + timeoutMs: z + .number({ error: 'ingress.webhook.timeoutMs must be a number' }) + .int('ingress.webhook.timeoutMs must be an integer') + .positive('ingress.webhook.timeoutMs must be a positive integer') + .default(30_000), + maxRetries: z + .number({ error: 'ingress.webhook.maxRetries must be a number' }) + .int('ingress.webhook.maxRetries must be an integer') + .nonnegative('ingress.webhook.maxRetries must be a non-negative integer') + .default(2), + initialBackoffMs: z + .number({ error: 'ingress.webhook.initialBackoffMs must be a number' }) + .int('ingress.webhook.initialBackoffMs must be an integer') + .positive('ingress.webhook.initialBackoffMs must be a positive integer') + .default(500), + maxPayloadBytes: z + .number({ error: 'ingress.webhook.maxPayloadBytes must be a number' }) + .int('ingress.webhook.maxPayloadBytes must be an integer') + .positive('ingress.webhook.maxPayloadBytes must be a positive integer') + .default(1_048_576), +}); + +export const IngressRateLimitConfigSchema = z.object({ + maxRequestsPerMinute: z + .number({ error: 'ingress.rateLimit.maxRequestsPerMinute must be a number' }) + .int('ingress.rateLimit.maxRequestsPerMinute must be an integer') + .nonnegative('ingress.rateLimit.maxRequestsPerMinute must be a non-negative integer') + .default(0), + maxRequestsPerHour: z + .number({ error: 'ingress.rateLimit.maxRequestsPerHour must be a number' }) + .int('ingress.rateLimit.maxRequestsPerHour must be an integer') + .nonnegative('ingress.rateLimit.maxRequestsPerHour must be a non-negative integer') + .default(0), +}); + const IngressBaseSchema = z.object({ enabled: z .boolean({ error: 'ingress.enabled must be a boolean' }) .optional(), publicBaseUrl: z .string({ error: 'ingress.publicBaseUrl must be a string' }) + .refine( + (val) => val === '' || /^https?:\/\//i.test(val), + 'ingress.publicBaseUrl must be an absolute URL starting with http:// or https://', + ) .default(''), + webhook: IngressWebhookConfigSchema.default({}), + rateLimit: IngressRateLimitConfigSchema.default({}), + shutdownDrainMs: z + .number({ error: 'ingress.shutdownDrainMs must be a number' }) + .int('ingress.shutdownDrainMs must be an integer') + .nonnegative('ingress.shutdownDrainMs must be a non-negative integer') + .default(5_000), }); export const IngressConfigSchema = IngressBaseSchema - .default({ publicBaseUrl: '' }) + .default({}) .transform((val) => ({ ...val, // Backward compatibility: if `enabled` was never explicitly set (undefined), @@ -222,5 +272,7 @@ export type ThinkingConfig = z.infer; export type ContextWindowConfig = z.infer; export type ModelPricingOverride = z.infer; export type SmsConfig = z.infer; +export type IngressWebhookConfig = z.infer; +export type IngressRateLimitConfig = z.infer; export type IngressConfig = z.infer; export type AssistantInboxConfig = z.infer; diff --git a/assistant/src/config/schema.ts b/assistant/src/config/schema.ts index a63391da353..7b784e5e2ac 100644 --- a/assistant/src/config/schema.ts +++ b/assistant/src/config/schema.ts @@ -101,6 +101,8 @@ export { ContextWindowConfigSchema, ModelPricingOverrideSchema, SmsConfigSchema, + IngressWebhookConfigSchema, + IngressRateLimitConfigSchema, IngressConfigSchema, AssistantInboxConfigSchema, } from './core-schema.js'; @@ -115,6 +117,8 @@ export type { ContextWindowConfig, ModelPricingOverride, SmsConfig, + IngressWebhookConfig, + IngressRateLimitConfig, IngressConfig, AssistantInboxConfig, } from './core-schema.js';