import { z } from "zod"; import { Backend, Capability, RiskLevel } from "./enums.js"; import { hash } from "./hash.js"; import { DbIntVersion } from "./version.js"; export type JsonValue = | null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }; export type JsonObject = { [key: string]: JsonValue }; export const JsonObject: z.ZodType = z.lazy(() => z .custom>(isPlainJsonRecordInput, { message: "expected plain JSON object", }) .superRefine((value, context) => { for (const key of Reflect.ownKeys(value)) { if (typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key)) { context.addIssue({ code: z.ZodIssueCode.custom, message: "expected plain JSON object", }); continue; } if (!isSafeJsonObjectKey(key)) { context.addIssue({ code: z.ZodIssueCode.custom, message: "reserved object key", path: [key], }); } } }) .pipe(z.record(z.string(), JsonValue)), ); export const JsonValue: z.ZodType = z.lazy(() => z.union([z.null(), z.boolean(), z.number().finite(), z.string(), JsonArray, JsonObject]), ); export const JsonArray: z.ZodType = z.lazy(() => z .custom(Array.isArray, { message: "expected JSON array" }) .superRefine((value, context) => { for (const key of Reflect.ownKeys(value)) { if (key === "length") { continue; } if (typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key)) { context.addIssue({ code: z.ZodIssueCode.custom, message: "expected JSON array", }); continue; } const index = Number(key); if ( !Number.isInteger(index) || index < 0 || index >= value.length || String(index) !== key ) { context.addIssue({ code: z.ZodIssueCode.custom, message: "expected JSON array", }); } } }) .pipe(z.array(JsonValue)), ); export const Persona = z .object({ name: z.string().min(1), version: DbIntVersion, backend: Backend, capabilities: z.array(Capability), maxRiskLevel: RiskLevel, allowedRoles: z.array(z.string().min(1)).optional(), promptConfig: z .object({ systemPrompt: z.string().optional(), instructionsPrelude: z.string().optional(), }) .strict() .default({}) .transform((value) => { const promptConfig: { systemPrompt?: string; instructionsPrelude?: string } = {}; if (value.systemPrompt !== undefined) { promptConfig.systemPrompt = value.systemPrompt; } if (value.instructionsPrelude !== undefined) { promptConfig.instructionsPrelude = value.instructionsPrelude; } return promptConfig; }), modelConfig: JsonObject.default({}), }) .strict(); export type Persona = z.infer; export function personaHash(persona: Persona | undefined): string { if (!persona) { throw new TypeError("persona is required"); } const hashSubject = { name: persona.name, version: persona.version, capabilities: persona.capabilities, backend: persona.backend, maxRiskLevel: persona.maxRiskLevel, promptConfig: persona.promptConfig, modelConfig: persona.modelConfig, }; return hash( persona.allowedRoles === undefined ? hashSubject : { ...hashSubject, allowedRoles: persona.allowedRoles }, ); } function isSafeJsonObjectKey(key: string) { return key !== "__proto__" && key !== "constructor" && key !== "prototype"; } function isPlainJsonRecordInput(value: unknown) { if (value === null || typeof value !== "object" || Array.isArray(value)) { return false; } const prototype = Object.getPrototypeOf(value); return prototype === Object.prototype || prototype === null; }