Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions app/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,23 +103,24 @@ async function submit(formData?: FormData, skip?: boolean) {
try {
const streamResult = await resolutionSearch(messages, timezone, drawnFeatures, location);

let partialObject: any;
let fullSummary = '';
for await (const partialObject of streamResult.partialObjectStream) {
for await (partialObject of streamResult.partialObjectStream) {
if (partialObject.summary) {
fullSummary = partialObject.summary;
summaryStream.update(fullSummary);
}
}

const analysisResult = await streamResult.object;
const analysisResult: any = await streamResult.object;
summaryStream.done(analysisResult.summary || 'Analysis complete.');

// Reconstruct standard GeoJSON from flattened schema if present
let geoJson: FeatureCollection | null = null;
if (analysisResult.geoJson && analysisResult.geoJson.features) {
geoJson = {
type: 'FeatureCollection',
features: analysisResult.geoJson.features.map(f => ({
features: analysisResult.geoJson.features.map( (f: any) => ({
type: 'Feature',
geometry: {
type: f.geometryType as any,
Expand Down Expand Up @@ -532,7 +533,26 @@ async function submit(formData?: FormData, skip?: boolean) {
errorOccurred = hasError

if (toolOutputs.length > 0) {
toolOutputs.map(output => {
await Promise.all(toolOutputs.map(async output => {
const result = output.result as any

if (output.toolName === 'calendarTool' && result.type === 'CALENDAR_ACTION') {
const { saveNote, getChatNotes } = await import('@/lib/actions/calendar')
if (result.action === 'create') {
const saved = await saveNote({
...result.note,
chatId: aiState.get().chatId,
userId: '', // set in saveNote
userTags: null,
mapFeatureId: null
})
output.result = { success: true, note: saved }
} else if (result.action === 'list') {
Comment on lines +546 to +554

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not emit success: true when note persistence fails.

Line 542 can return null from saveNote, but Line 549 always reports success. That creates false confirmations and inconsistent chat/calendar state.

Suggested fix
              if (result.action === 'create') {
                 const saved = await saveNote({
                   ...result.note,
                   chatId: aiState.get().chatId,
                   userId: '', // set in saveNote
                   userTags: null,
                   mapFeatureId: null
                 })
-                output.result = { success: true, note: saved }
+                output.result = saved
+                  ? { success: true, note: saved }
+                  : { success: false, error: 'Failed to save calendar note' }
              } else if (result.action === 'list') {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/actions.tsx` around lines 542 - 550, The code unconditionally sets
output.result = { success: true, note: saved } even though saveNote(...) can
return null; update the block that calls saveNote (using result.note and
aiState.get().chatId) to check the returned value (saved) and only set success:
true and include the note when saved is non-null; if saved is null, set
output.result = { success: false } (and optionally include an error message) and
avoid mutating chat/calendar state or treating it as persisted. Ensure you
reference the saveNote call and output.result assignment so the conditional
wraps only the successful-persistence path.

const notes = await getChatNotes(aiState.get().chatId)
output.result = { success: true, notes }
}
}

aiState.update({
...aiState.get(),
messages: [
Expand All @@ -546,7 +566,7 @@ async function submit(formData?: FormData, skip?: boolean) {
}
]
})
})
}))
}
}

Expand Down
1,421 changes: 969 additions & 452 deletions bun.lock

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions components/calendar-toggle-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ export const CalendarToggleProvider = ({ children }: { children: ReactNode }) =>
const [isCalendarOpen, setIsCalendarOpen] = useState(false)

const toggleCalendar = () => {
startTransition(() => {
setIsCalendarOpen(prevState => !prevState)
})
setIsCalendarOpen(prevState => !prevState)
}

return (
Expand Down
6 changes: 3 additions & 3 deletions components/map/mapbox-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ export const Mapbox: React.FC<{ position?: { latitude: number; longitude: number
map.current.off('draw.create', updateMeasurementLabels)
map.current.off('draw.delete', updateMeasurementLabels)
map.current.off('draw.update', updateMeasurementLabels)
map.current.removeControl(drawRef.current)
map.current.removeControl(drawRef.current as any)
drawRef.current = null

// Clean up any existing labels
Expand Down Expand Up @@ -282,7 +282,7 @@ export const Mapbox: React.FC<{ position?: { latitude: number; longitude: number
})

// Add control to map
map.current.addControl(drawRef.current, 'top-right')
map.current.addControl(drawRef.current as any, 'top-right')

// Add navigation control only on desktop
if (window.innerWidth > 768) {
Expand Down Expand Up @@ -509,7 +509,7 @@ export const Mapbox: React.FC<{ position?: { latitude: number; longitude: number
map.current.off('draw.create', updateMeasurementLabels)
map.current.off('draw.delete', updateMeasurementLabels)
map.current.off('draw.update', updateMeasurementLabels)
map.current.removeControl(drawRef.current)
map.current.removeControl(drawRef.current as any)
drawRef.current = null

// Clean up any existing labels
Expand Down
3 changes: 2 additions & 1 deletion components/message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ export function BotMessage({ content }: { content: StreamableValue<string> }) {

return (
<div className="overflow-x-auto">
<div className="prose-sm prose-neutral prose-a:text-accent-foreground/50">
<MemoizedReactMarkdown
rehypePlugins={[[rehypeExternalLinks, { target: '_blank' }], rehypeKatex]}
remarkPlugins={[remarkGfm, remarkMath]}
className="prose-sm prose-neutral prose-a:text-accent-foreground/50"
>
{processedData}
</MemoizedReactMarkdown>
</div>
</div>
)
}
Expand Down
2 changes: 1 addition & 1 deletion components/theme-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import * as React from 'react'
import { ThemeProvider as NextThemesProvider } from 'next-themes'
import { type ThemeProviderProps } from 'next-themes/dist/types'
import { type ThemeProviderProps } from 'next-themes'

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
Expand Down
3 changes: 1 addition & 2 deletions components/ui/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@ import ReactMarkdown, { Options } from 'react-markdown'
export const MemoizedReactMarkdown: FC<Options> = memo(
ReactMarkdown,
(prevProps, nextProps) =>
prevProps.children === nextProps.children &&
prevProps.className === nextProps.className
prevProps.children === nextProps.children
Comment on lines 6 to +7

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify where MemoizedReactMarkdown is used and whether non-children props vary.
rg -n -C3 '\bMemoizedReactMarkdown\b'
rg -n -C3 '<MemoizedReactMarkdown[^>]*className=|<MemoizedReactMarkdown[^>]*components=|<MemoizedReactMarkdown[^>]*remarkPlugins=|<MemoizedReactMarkdown[^>]*rehypePlugins='

Repository: QueueLab/QCX

Length of output: 1627


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== components/ui/markdown.tsx (lines 1-120) ==="
nl -ba components/ui/markdown.tsx | sed -n '1,120p'

echo "=== components/message.tsx (around MemoizedReactMarkdown usage) ==="
nl -ba components/message.tsx | sed -n '1,120p'

Repository: QueueLab/QCX

Length of output: 166


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== components/ui/markdown.tsx (first 160 lines) ==="
cat -n components/ui/markdown.tsx | sed -n '1,160p'

echo "=== components/message.tsx (first 220 lines) ==="
cat -n components/message.tsx | sed -n '1,220p'

Repository: QueueLab/QCX

Length of output: 2287


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "MemoizedReactMarkdown" -S

Repository: QueueLab/QCX

Length of output: 332


Custom memo comparator only checks children—brittle for future props, but not a current issue here.

MemoizedReactMarkdown is only used in components/message.tsx, and that call site always passes the same remarkPlugins/rehypePlugins values (even though the arrays/options are re-created each render). Since markdown content changes drive children, the component still updates when needed. The main risk is for future call sites where non-children props change while children stays the same; in that case this comparator could suppress updates. Consider removing the custom comparator or using a comparator that accounts for the relevant props.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/ui/markdown.tsx` around lines 6 - 7, The custom memo comparator
currently used for MemoizedReactMarkdown only compares prevProps.children ===
nextProps.children which can suppress updates if other props change; update the
memo usage by either removing the custom comparator so React.memo uses default
shallow prop comparison, or replace the comparator with a shallow equality that
also checks remarkPlugins and rehypePlugins (and any other passed props) by
comparing prevProps.remarkPlugins === nextProps.remarkPlugins &&
prevProps.rehypePlugins === nextProps.rehypePlugins && prevProps.children ===
nextProps.children; locate the memo wrapper around the ReactMarkdown component
(the function using (prevProps, nextProps) => prevProps.children ===
nextProps.children and the exported MemoizedReactMarkdown) and apply one of
these fixes.

)
27 changes: 27 additions & 0 deletions lib/actions/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,33 @@ export async function getNotes(date: Date, chatId: string | null): Promise<Calen
}
}

/**
* Retrieves all notes for a specific chat session.
* @param chatId - The ID of the chat session.
* @returns A promise that resolves to an array of notes.
*/
export async function getChatNotes(chatId: string): Promise<CalendarNote[]> {
const userId = await getCurrentUserIdOnServer()
if (!userId) {
console.error('getChatNotes: User not authenticated')
return []
}

try {
const notes = await db
.select()
.from(calendarNotes)
.where(and(eq(calendarNotes.userId, userId), eq(calendarNotes.chatId, chatId)))
.orderBy(desc(calendarNotes.date))
.execute()

return notes
} catch (error) {
console.error('Error fetching chat notes:', error)
return []
}
}

/**
* Saves a new note or updates an existing one.
* @param noteData - The note data to save.
Expand Down
2 changes: 1 addition & 1 deletion lib/actions/suggest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export async function getSuggestions(

;(async () => {
const result = await streamObject({
model: (await getModel()) as LanguageModel,
model: (await getModel()) as any,
system: systemPrompt,
messages: [{ role: 'user', content: query }],
schema: relatedSchema
Expand Down
2 changes: 1 addition & 1 deletion lib/agents/inquire.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export async function inquire(

let finalInquiry: PartialInquiry = {};
const result = await streamObject({
model: (await getModel()) as LanguageModel,
model: (await getModel()) as any,
system: `You are a helpful assistant that gathers clarifying information from the user.
Generate a structured inquiry with a clear question, multiple choice options, and optionally allow free-text input.
Ensure the inquiry is concise and helps narrow down the user's intent.`,
Expand Down
2 changes: 1 addition & 1 deletion lib/agents/query-suggestor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export async function querySuggestor(

// OPTIMIZATION: Use a more concise system prompt to reduce token usage
const result = await streamObject({
model: (await getModel()) as LanguageModel,
model: (await getModel()) as any,
system: `Generate 3 follow-up queries that explore the subject matter deeper. Format as JSON with an "items" array containing objects with "query" fields. Keep queries concise and relevant.`,
messages,
schema: relatedSchema,
Expand Down
2 changes: 1 addition & 1 deletion lib/agents/researcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export async function researcher(
})

const result = await nonexperimental_streamText({
model: (await getModel(hasImage)) as LanguageModel,
model: (await getModel(hasImage)) as any,
maxTokens: 4096,
system: systemPromptToUse,
messages,
Expand Down
2 changes: 1 addition & 1 deletion lib/agents/resolution-search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ Analyze the user's prompt and the image to provide a holistic understanding of t

// Use streamObject to get partial results.
return streamObject({
model: await getModel(hasImage),
model: (await getModel(hasImage)) as any,
system: systemPrompt,
messages: filteredMessages,
schema: resolutionSearchSchema,
Expand Down
2 changes: 1 addition & 1 deletion lib/agents/task-manager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function taskManager(messages: CoreMessage[]) {
}

const result = await generateObject({
model: (await getModel()) as LanguageModel,
model: (await getModel()) as any,
system: `As a planet computer, your primary objective is to act as an efficient **Task Manager** for the user's query. Your goal is to minimize unnecessary steps and maximize the efficiency of the subsequent exploration phase (researcher agent).

You must first analyze the user's input and determine the optimal course of action. You have two options at your disposal:
Expand Down
44 changes: 44 additions & 0 deletions lib/agents/tools/calendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { tool } from 'ai'
import { calendarSchema } from '@/lib/schema/calendar'
import { saveNote, getChatNotes } from '@/lib/actions/calendar'
import { ToolProps } from '.'
import { Section } from '@/components/section'
import { BotMessage } from '@/components/message'
import { nanoid } from '@/lib/utils'

export const calendarTool = ({ uiStream }: ToolProps) =>
tool({
description: 'Create reminders, add notes to the calendar, or list existing notes for the current chat.',
parameters: calendarSchema,
execute: async ({ action, content, date, location }) => {
// In a real execution, we need the chatId.
// Since tools are called within the submit action context,
// we might need to find a way to pass chatId here if it's not readily available.
// For this implementation, we'll assume the AI state provides it or we handle it in app/actions.

if (action === 'create_note' && content) {
// We'll return a result that the caller (submit action) can use to actually save
return {
type: 'CALENDAR_ACTION',
action: 'create',
note: {
content,
date: date ? new Date(date) : new Date(),
locationTags: location ? {
type: 'Point',
coordinates: [location.longitude, location.latitude]
} : null
}
}
}

if (action === 'list_notes') {
return {
type: 'CALENDAR_ACTION',
action: 'list'
}
}

return { error: 'Invalid calendar action' }
}
})
5 changes: 5 additions & 0 deletions lib/agents/tools/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { retrieveTool } from './retrieve'
import { searchTool } from './search'
import { videoSearchTool } from './video-search'
import { geospatialTool } from './geospatial' // Removed useGeospatialToolMcp import
import { calendarTool } from './calendar'

import { MapProvider } from '@/lib/store/settings'

Expand All @@ -21,6 +22,10 @@ export const getTools = ({ uiStream, fullResponse, mapProvider }: ToolProps) =>
geospatialQueryTool: geospatialTool({
uiStream,
mapProvider
}),
calendarTool: calendarTool({
uiStream,
fullResponse
})
}

Expand Down
2 changes: 1 addition & 1 deletion lib/agents/writer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export async function writer(
const systemToUse = dynamicSystemPrompt && dynamicSystemPrompt.trim() !== '' ? dynamicSystemPrompt : default_system_prompt;

const result = await nonexperimental_streamText({
model: (await getModel()) as LanguageModel,
model: (await getModel()) as any,
maxTokens: 2500,
system: systemToUse, // Use the dynamic or default system prompt
messages
Expand Down
14 changes: 14 additions & 0 deletions lib/schema/calendar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { z } from 'zod'

export const calendarSchema = z.object({
action: z.string().describe('The action to perform: "create_note" or "list_notes"'),
content: z.string().optional().describe('The content of the note or reminder'),
date: z.string().optional().describe('The date for the note/reminder in ISO format'),
Comment on lines +4 to +6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file and print the relevant section with line numbers
if [ -f "lib/schema/calendar.ts" ]; then
  echo "== lib/schema/calendar.ts =="
  nl -ba lib/schema/calendar.ts | sed -n '1,120p'
else
  echo "File lib/schema/calendar.ts not found"
  exit 1
fi

# Search for other schema patterns (enums/iso validation) in lib/schema
echo "== Search: z.enum / iso date usage in lib/schema =="
rg -n "z\.enum\(|iso\.date\(|iso\.datetime\(|zod.*iso" lib/schema || true

# Search for "calendarSchema" usage to see expected payloads
echo "== Search: calendarSchema usage =="
rg -n "calendarSchema" -S . || true

Repository: QueueLab/QCX

Length of output: 146


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="lib/schema/calendar.ts"

if [ -f "$FILE" ]; then
  echo "== $FILE (first 160 lines) =="
  cat -n "$FILE" | sed -n '1,160p'
else
  echo "File $FILE not found"
  exit 1
fi

echo
echo "== Search: z.enum / iso.date / iso.datetime patterns in lib/schema =="
rg -n --hidden --glob 'lib/schema/**/*' "z\.enum\(|iso\.date\(|iso\.datetime\(" . || true

echo
echo "== Search: calendarSchema usage =="
rg -n "calendarSchema" -S . || true

Repository: QueueLab/QCX

Length of output: 1778


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== lib/agents/tools/calendar.tsx =="
if [ -f "lib/agents/tools/calendar.tsx" ]; then
  cat -n "lib/agents/tools/calendar.tsx" | sed -n '1,220p'
else
  echo "File lib/agents/tools/calendar.tsx not found"
fi

echo
echo "== Search: ISO date / parse / z.string().datetime patterns =="
rg -n --hidden "iso\.date|iso\.datetime|datetime\(|Date\.parse|new Date\(|/ISO/i|ISO format|regex.*date" . || true

echo
echo "== Search: action create_note/list_notes usage =="
rg -n --hidden "create_note|list_notes|calendarSchema\.shape|parameters\.action|action ===" lib || true

Repository: QueueLab/QCX

Length of output: 6057


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== lib/actions/calendar.ts =="
if [ -f "lib/actions/calendar.ts" ]; then
  cat -n lib/actions/calendar.ts | sed -n '1,220p'
else
  echo "File lib/actions/calendar.ts not found"
fi

echo
echo "== tests/calendar.spec.ts =="
if [ -f "tests/calendar.spec.ts" ]; then
  cat -n tests/calendar.spec.ts | sed -n '1,200p'
else
  echo "File tests/calendar.spec.ts not found"
fi

Repository: QueueLab/QCX

Length of output: 12226


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Find calendarNotes schema =="
rg -n "calendarNotes" lib/db/schema -S || true

# Try to locate the file that defines calendarNotes
FILE=$(fd -a "schema.*" lib/db || true | head -n 20 | head -n 1)
echo "Sample schema files under lib/db: "
fd -a "schema*" lib/db | head -n 30 || true

# Search for the exact field name "date" within calendarNotes definition
rg -n "calendarNotes.*date|date.*calendarNotes" lib/db -S || true

echo
echo "== Find NewCalendarNote / CalendarNote types =="
rg -n "type (NewCalendarNote|CalendarNote)|export type (NewCalendarNote|CalendarNote)" lib -S || true

rg -n "NewCalendarNote" lib/types lib -S || true

echo
echo "== Inspect lib/db/schema directory for calendarNotes file =="
fd -a "calendar*.*" lib/db/schema lib/db || true

Repository: QueueLab/QCX

Length of output: 994


🏁 Script executed:

#!/bin/bash
set -euo pipefail

SCHEMA_FILE="lib/db/schema.ts"
TYPES_FILE="lib/types/index.ts"

echo "== Inspect $SCHEMA_FILE (calendarNotes definition) =="
if [ -f "$SCHEMA_FILE" ]; then
  # show only the regions likely containing calendarNotes
  rg -n "calendarNotes" "$SCHEMA_FILE" -S
  echo
  line=$(rg -n "calendarNotes" "$SCHEMA_FILE" -S | head -n 1 | cut -d: -f1 || true)
  if [ -n "${line:-}" ]; then
    start=$((line-40)); end=$((line+120))
    start=$((start<1 ? 1 : start))
    sed -n "${start},${end}p" "$SCHEMA_FILE" | cat -n
  fi
else
  echo "Missing $SCHEMA_FILE"
fi

echo
echo "== Inspect $TYPES_FILE (CalendarNote/NewCalendarNote) =="
if [ -f "$TYPES_FILE" ]; then
  rg -n "export type (CalendarNote|NewCalendarNote)|calendarNotes|date:" "$TYPES_FILE" -S
  echo
  rg -n "export type CalendarNote" "$TYPES_FILE" -S
  line=$(rg -n "export type CalendarNote" "$TYPES_FILE" -S | head -n 1 | cut -d: -f1 || true)
  if [ -n "${line:-}" ]; then
    start=$((line-20)); end=$((line+80))
    start=$((start<1 ? 1 : start))
    sed -n "${start},${end}p" "$TYPES_FILE" | cat -n
  fi
else
  echo "Missing $TYPES_FILE"
fi

Repository: QueueLab/QCX

Length of output: 7694


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "CALENDAR_ACTION" -S . || true
rg -n "type: 'CALENDAR_ACTION'" -S . || true

echo
echo "== Inspect likely handler in app/actions.tsx (search around CALENDAR_ACTION) =="
FILE="app/actions.tsx"
if [ -f "$FILE" ]; then
  line=$(rg -n "CALENDAR_ACTION" "$FILE" -S | head -n 1 | cut -d: -f1 || true)
  if [ -n "${line:-}" ]; then
    start=$((line-80)); end=$((line+120))
    start=$((start<1 ? 1 : start))
    sed -n "${start},${end}p" "$FILE" | cat -n
  else
    echo "No CALENDAR_ACTION in $FILE"
  fi
else
  echo "Missing $FILE"
fi

Repository: QueueLab/QCX

Length of output: 6980


Validate action and date at the calendarSchema boundary.

  • action is currently z.string(), so arbitrary values reach runtime (where only create_note/list_notes are handled).
  • date is currently z.string(), and the tool does new Date(date) without ISO validation; invalid strings can result in Invalid Date reaching the DB insert.
Suggested fix
 export const calendarSchema = z.object({
-  action: z.string().describe('The action to perform: "create_note" or "list_notes"'),
+  action: z
+    .enum(['create_note', 'list_notes'])
+    .describe('The action to perform: "create_note" or "list_notes"'),
   content: z.string().optional().describe('The content of the note or reminder'),
-  date: z.string().optional().describe('The date for the note/reminder in ISO format'),
+  date: z
+    .union([z.iso.datetime(), z.iso.date()])
+    .optional()
+    .describe('The date for the note/reminder in ISO format'),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/schema/calendar.ts` around lines 4 - 6, Update calendarSchema to validate
both fields at the schema level: replace the loose action: z.string() with a
z.enum(['create_note','list_notes']) (or equivalent) so only those two actions
are allowed, and tighten date: z.string().optional() to validate ISO dates (e.g.
use z.string().optional().refine(d => !d || !isNaN(Date.parse(d)), { message:
'invalid ISO date' }) or a preprocess + z.string().refine check) so invalid
strings are rejected before runtime; adjust any callers of
calendarSchema.parse/validate to handle schema errors accordingly.

location: z.object({
latitude: z.number(),
longitude: z.number(),
place_name: z.string().optional()
}).optional().describe('Optional location to associate with the note')
})

export type CalendarToolSchema = z.infer<typeof calendarSchema>
3 changes: 2 additions & 1 deletion lib/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export async function getModel(requireVision: boolean = false) {

if (awsAccessKeyId && awsSecretAccessKey) {
const bedrock = createAmazonBedrock({
// @ts-ignore
bedrockOptions: {
region: awsRegion,
credentials: {
Expand All @@ -113,7 +114,7 @@ export async function getModel(requireVision: boolean = false) {
},
},
});
const model = bedrock(bedrockModelId, {
const model = (bedrock as any)(bedrockModelId, {
additionalModelRequestFields: { top_k: 350 },
});
return model;
Expand Down
Loading