2Branches0Tags
GL
glucryptoFix trade candle loading via gateway
typescript
import { z } from "zod"; const CONFIG_ENV = "OPENPOND_DAILY_SNAPSHOT_CONFIG"; const TEMPLATE_CONFIG_ENV_VAR = CONFIG_ENV; const TEMPLATE_CONFIG_VERSION = 2; export type ConnectedApp = { appId: string; deploymentId: string; toolName: string; displayName?: string; method?: "GET" | "POST"; body?: unknown; }; export type ScheduleConfig = { cron: string; enabled: boolean; notifyEmail: boolean; }; const tradeReviewSchema = z.object({ useRecentTrades: z.boolean().optional(), riskProfile: z.enum(["conservative", "balanced", "aggressive"]).optional(), allowHighLeverage: z.boolean().optional(), }); export const subAgentSchema = z.object({ appId: z.string().min(1), deploymentId: z.string().min(1), toolName: z.string().min(1), displayName: z.string().optional(), method: z.enum(["GET", "POST"]).optional(), body: z.unknown().optional(), }); export const configSchema = z.object({ configVersion: z.number().int().optional(), subAgents: z.array(subAgentSchema).optional(), channels: z .object({ email: z.boolean().optional(), telegram: z.boolean().optional(), }) .optional(), schedule: z .object({ cron: z.string().min(1).optional(), enabled: z.boolean().optional(), notifyEmail: z.boolean().optional(), }) .optional(), summaryModel: z.string().min(1).optional(), historyLimit: z.number().int().min(1).max(10).optional(), tradeReview: tradeReviewSchema.optional(), }); export type DailySummaryConfig = z.infer<typeof configSchema>; export type TradeReviewConfig = NonNullable<DailySummaryConfig["tradeReview"]>; const TEMPLATE_CONFIG_DEFAULTS: DailySummaryConfig = { configVersion: TEMPLATE_CONFIG_VERSION, channels: { email: false, }, schedule: { cron: "0 8 * * *", enabled: false, notifyEmail: true, }, summaryModel: "gpt-5-mini", historyLimit: 3, tradeReview: { useRecentTrades: true, riskProfile: "balanced", allowHighLeverage: false, }, }; const TEMPLATE_CONFIG_SCHEMA = { type: "object", additionalProperties: false, properties: { tradeReview: { type: "object", title: "Trade review preferences", additionalProperties: false, properties: { useRecentTrades: { type: "boolean", title: "Use recent trades", description: "Whether trade review should factor in your recent manual trading history.", }, riskProfile: { type: "string", title: "Risk profile", enum: ["conservative", "balanced", "aggressive"], description: "Controls how strict the review should be about aggressive setups.", }, allowHighLeverage: { type: "boolean", title: "Allow high leverage", description: "Treat elevated leverage as acceptable unless the liquidation and TP/SL structure are weak.", }, }, }, summaryModel: { type: "string", title: "Summary model", description: "Model used for daily summaries and trade review narration.", }, historyLimit: { type: "number", title: "History items", minimum: 1, maximum: 10, description: "How many prior digest runs to use for change summaries.", }, }, }; function normalizeToolName(value: string | undefined) { return (value ?? "").trim().toLowerCase(); } function parseJson(raw: string | null): unknown | null { if (!raw) return null; try { return JSON.parse(raw) as unknown; } catch { return null; } } function mergeConfig( base: DailySummaryConfig, override: DailySummaryConfig, ): DailySummaryConfig { return { ...base, ...override, channels: { ...base.channels, ...override.channels, }, schedule: { ...base.schedule, ...override.schedule, }, tradeReview: { ...base.tradeReview, ...override.tradeReview, }, subAgents: override.subAgents ?? base.subAgents, }; } export async function readConfig(): Promise<DailySummaryConfig> { let config: DailySummaryConfig = TEMPLATE_CONFIG_DEFAULTS; const envConfig = parseJson(process.env[CONFIG_ENV] ?? null); if (envConfig && configSchema.safeParse(envConfig).success) { config = mergeConfig(config, envConfig as DailySummaryConfig); } return config; } export function resolveConnectedApps(): ConnectedApp[] { const raw = process.env[CONFIG_ENV]; if (!raw) return []; try { const parsed = JSON.parse(raw) as { subAgents?: unknown }; const subAgents = Array.isArray(parsed?.subAgents) ? parsed.subAgents : []; const seenToolNames = new Set<string>(); return subAgents .map((agent) => { if (!agent || typeof agent !== "object") return null; const record = agent as Record<string, unknown>; const appId = typeof record.appId === "string" ? record.appId : ""; const deploymentId = typeof record.deploymentId === "string" ? record.deploymentId : ""; const toolName = typeof record.toolName === "string" ? record.toolName : ""; if (!appId || !deploymentId || !toolName) return null; const connectedApp: ConnectedApp = { appId, deploymentId, toolName, }; if ( typeof record.displayName === "string" && record.displayName.trim().length > 0 ) { connectedApp.displayName = record.displayName; } if (record.method === "GET" || record.method === "POST") { connectedApp.method = record.method; } if ("body" in record) { connectedApp.body = record.body; } return connectedApp; }) .filter((item): item is ConnectedApp => { if (item === null) return false; const toolKey = normalizeToolName(item.toolName); if (!toolKey || seenToolNames.has(toolKey)) { return false; } seenToolNames.add(toolKey); return true; }); } catch { return []; } } export function resolveScheduleConfig(): ScheduleConfig { const fallback: ScheduleConfig = { cron: "0 8 * * *", enabled: false, notifyEmail: true, }; const raw = process.env[CONFIG_ENV]; if (!raw) return fallback; try { const parsed = JSON.parse(raw) as { schedule?: { cron?: unknown; enabled?: unknown; notifyEmail?: unknown }; channels?: { email?: unknown }; }; const cronRaw = parsed?.schedule?.cron; const cron = typeof cronRaw === "string" && cronRaw.trim().length > 0 ? cronRaw.trim() : fallback.cron; const enabled = parsed?.schedule?.enabled === true; const notifyEmail = typeof parsed?.schedule?.notifyEmail === "boolean" ? parsed.schedule.notifyEmail : typeof parsed?.channels?.email === "boolean" ? parsed.channels.email : fallback.notifyEmail; return { cron, enabled, notifyEmail }; } catch { return fallback; } } export function resolveTradeReviewConfig(config: DailySummaryConfig): Required<TradeReviewConfig> { return { useRecentTrades: config.tradeReview?.useRecentTrades ?? true, riskProfile: config.tradeReview?.riskProfile ?? "balanced", allowHighLeverage: config.tradeReview?.allowHighLeverage ?? false, }; } export const MY_OPENPOND_TEMPLATE_CONFIG = { version: TEMPLATE_CONFIG_VERSION, schema: TEMPLATE_CONFIG_SCHEMA, defaults: TEMPLATE_CONFIG_DEFAULTS, envVar: TEMPLATE_CONFIG_ENV_VAR, };