( input: string, format: 'date' | 'date-time', signal: AbortSignal, )
| 21 | * @returns Parsed ISO 8601 string or error message |
| 22 | */ |
| 23 | export async function parseNaturalLanguageDateTime( |
| 24 | input: string, |
| 25 | format: 'date' | 'date-time', |
| 26 | signal: AbortSignal, |
| 27 | ): Promise<DateTimeParseResult> { |
| 28 | // Get current datetime with timezone for context |
| 29 | const now = new Date() |
| 30 | const currentDateTime = now.toISOString() |
| 31 | const timezoneOffset = -now.getTimezoneOffset() // minutes, inverted sign |
| 32 | const tzHours = Math.floor(Math.abs(timezoneOffset) / 60) |
| 33 | const tzMinutes = Math.abs(timezoneOffset) % 60 |
| 34 | const tzSign = timezoneOffset >= 0 ? '+' : '-' |
| 35 | const timezone = `${tzSign}${String(tzHours).padStart(2, '0')}:${String(tzMinutes).padStart(2, '0')}` |
| 36 | const dayOfWeek = now.toLocaleDateString('en-US', { weekday: 'long' }) |
| 37 | |
| 38 | // Build system prompt with context |
| 39 | const systemPrompt = asSystemPrompt([ |
| 40 | 'You are a date/time parser that converts natural language into ISO 8601 format.', |
| 41 | 'You MUST respond with ONLY the ISO 8601 formatted string, with no explanation or additional text.', |
| 42 | 'If the input is ambiguous, prefer future dates over past dates.', |
| 43 | "For times without dates, use today's date.", |
| 44 | 'For dates without times, do not include a time component.', |
| 45 | 'If the input is incomplete or you cannot confidently parse it into a valid date, respond with exactly "INVALID" (nothing else).', |
| 46 | 'Examples of INVALID input: partial dates like "2025-01-", lone numbers like "13", gibberish.', |
| 47 | 'Examples of valid natural language: "tomorrow", "next Monday", "jan 1st 2025", "in 2 hours", "yesterday".', |
| 48 | ]) |
| 49 | |
| 50 | // Build user prompt with rich context |
| 51 | const formatDescription = |
| 52 | format === 'date' |
| 53 | ? 'YYYY-MM-DD (date only, no time)' |
| 54 | : `YYYY-MM-DDTHH:MM:SS${timezone} (full date-time with timezone)` |
| 55 | |
| 56 | const userPrompt = `Current context: |
| 57 | - Current date and time: ${currentDateTime} (UTC) |
| 58 | - Local timezone: ${timezone} |
| 59 | - Day of week: ${dayOfWeek} |
| 60 | |
| 61 | User input: "${input}" |
| 62 | |
| 63 | Output format: ${formatDescription} |
| 64 | |
| 65 | Parse the user's input into ISO 8601 format. Return ONLY the formatted string, or "INVALID" if the input is incomplete or unparseable.` |
| 66 | |
| 67 | try { |
| 68 | const result = await queryHaiku({ |
| 69 | systemPrompt, |
| 70 | userPrompt, |
| 71 | signal, |
| 72 | options: { |
| 73 | querySource: 'mcp_datetime_parse', |
| 74 | agents: [], |
| 75 | isNonInteractiveSession: false, |
| 76 | hasAppendSystemPrompt: false, |
| 77 | mcpTools: [], |
| 78 | enablePromptCaching: false, |
| 79 | }, |
| 80 | }) |
no test coverage detected