cron.ts112 lines · main
1import { codeBlock } from 'common-tags'
2import { jsonrepair } from 'jsonrepair'
3import type OpenAI from 'openai'
4
5import { ContextLengthError } from '../errors'
6
7// Responds to a natural language request for cron syntax.
8export async function generateCron(openai: OpenAI, prompt: string) {
9 const initMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
10 {
11 role: 'system',
12 content: codeBlock`
13 You are a cron syntax expert. Your purpose is to convert natural language time descriptions into valid cron expressions for pg_cron.
14
15 Rules for responses:
16 - For standard intervals (minutes and above), output cron expressions in the 5-field format supported by pg_cron
17 - For second-based intervals, use the special pg_cron "x seconds" syntax
18 - Do not provide any explanation of what the cron expression does
19 - Format output as markdown with the cron expression in a code block
20 - Do not ask for clarification if you need it. Just output the cron expression.
21
22 Example input: "Every Monday at 3am"
23 Example output:
24 \`\`\`
25 0 3 * * 1
26 \`\`\`
27
28 Example input: "Every 30 seconds"
29 Example output:
30 \`\`\`
31 30 seconds
32 \`\`\`
33
34 Additional examples:
35 - Every minute: \`* * * * *\`
36 - Every 5 minutes: \`*/5 * * * *\`
37 - Every first of the month, at 00:00: \`0 0 1 * *\`
38 - Every night at midnight: \`0 0 * * *\`
39 - Every Monday at 2am: \`0 2 * * 1\`
40 - Every 15 seconds: \`15 seconds\`
41 - Every 45 seconds: \`45 seconds\`
42
43 Field order for standard cron:
44 - minute (0-59)
45 - hour (0-23)
46 - day (1-31)
47 - month (1-12)
48 - weekday (0-6, Sunday=0)
49
50 Important: pg_cron uses "x seconds" for second-based intervals, not "x * * * *".
51 If the user asks for seconds, do not use the 5-field format, instead use "x seconds".
52
53 Here is the user's prompt:
54 ${prompt}
55 `,
56 },
57 ]
58
59 const completionFunction = {
60 name: 'generateCronSyntax',
61 description: 'Generates a cron expression for a given natural language description.',
62 parameters: {
63 type: 'object',
64 properties: {
65 cron_expression: {
66 type: 'string',
67 description: 'The generated cron expression.',
68 },
69 },
70 required: ['cron_expression'],
71 },
72 }
73
74 try {
75 const response = await openai.chat.completions.create({
76 model: 'gpt-4o-mini-2024-07-18',
77 messages: initMessages,
78 max_tokens: 1024,
79 temperature: 0,
80 stream: false,
81 tool_choice: {
82 type: 'function',
83 function: {
84 name: completionFunction.name,
85 },
86 },
87 tools: [
88 {
89 type: 'function',
90 function: completionFunction,
91 },
92 ],
93 })
94
95 // Parse the JSON string in arguments using jsonrepair
96 const functionArgs = response?.choices?.[0]?.message?.tool_calls?.[0]?.function?.arguments
97 if (!functionArgs) {
98 throw new Error('No cron expression generated')
99 }
100
101 // Use jsonrepair before parsing
102 const repairedJsonString = jsonrepair(functionArgs)
103 const args = JSON.parse(repairedJsonString)
104
105 return args.cron_expression
106 } catch (error) {
107 if (error instanceof Error && 'code' in error && error.code === 'context_length_exceeded') {
108 throw new ContextLengthError()
109 }
110 throw error
111 }
112}