Calendar integration: add timezone config and fix tool call parsing

- Add timezone field to CalendarConfig for local timezone support
- Update CustomProvider to parse calendar tool calls from JSON in LLM responses
- Add pytz dependency to pyproject.toml for timezone handling
This commit is contained in:
tanyar09 2026-03-06 12:42:45 -05:00
parent bc53dc6535
commit 6364a195c5
3 changed files with 41 additions and 7 deletions

View File

@ -252,6 +252,7 @@ class CalendarConfig(Base):
token_file: str = "" # Path to store OAuth2 token (default: ~/.nanobot/calendar_token.json)
calendar_id: str = "primary" # Calendar ID to use (default: primary calendar)
auto_schedule_from_email: bool = True # Automatically schedule meetings from emails
timezone: str = "UTC" # Timezone for parsing times (e.g., "America/New_York", "Europe/London", "UTC")
class ExecToolConfig(Base):

View File

@ -62,22 +62,37 @@ class CustomProvider(LLMProvider):
# If no structured tool calls, try to parse from content (Ollama sometimes returns JSON in content)
# Only parse if content looks like it contains a tool call JSON (to avoid false positives)
content = msg.content or ""
if not tool_calls and content and '"name"' in content and '"parameters"' in content:
# Check for standard format: {"name": "...", "parameters": {...}}
has_standard_format = '"name"' in content and '"parameters"' in content
# Check for calendar tool format: {"action": "...", ...}
has_calendar_format = '"action"' in content and ("calendar" in content.lower() or any(action in content for action in ["list_events", "create_event", "update_event", "delete_event"]))
if not tool_calls and content and (has_standard_format or has_calendar_format):
import re
# Look for JSON tool call patterns: {"name": "exec", "parameters": {...}}
# Look for JSON tool call patterns: {"name": "exec", "parameters": {...}} or {"action": "list_events", ...}
# Find complete JSON objects by matching braces
pattern = r'\{\s*"name"\s*:\s*"(\w+)"'
# Try "action" pattern first (for calendar tool), then "name" pattern
patterns = [
(r'\{\s*"action"\s*:\s*"(\w+)"', "action"), # Calendar tool format
(r'\{\s*"name"\s*:\s*"(\w+)"', "name"), # Standard format
]
start_pos = 0
max_iterations = 5 # Safety limit
max_iterations = 10 # Increased for multiple patterns
iteration = 0
while iteration < max_iterations:
iteration += 1
match = re.search(pattern, content[start_pos:])
match = None
pattern_type = None
for pattern, ptype in patterns:
match = re.search(pattern, content[start_pos:])
if match:
pattern_type = ptype
break
if not match:
break
json_start = start_pos + match.start()
name = match.group(1)
key_value = match.group(1)
# Find the matching closing brace by counting braces
brace_count = 0
@ -98,7 +113,24 @@ class CustomProvider(LLMProvider):
try:
json_str = content[json_start:json_end]
tool_obj = json_repair.loads(json_str)
# Only accept if it has both name and parameters, and name is a valid tool name
# Handle calendar tool format: {"action": "...", ...}
if isinstance(tool_obj, dict) and "action" in tool_obj:
# This is a calendar tool call in JSON format
action = tool_obj.get("action")
if action and action in ["list_events", "create_event", "update_event", "delete_event", "delete_events", "check_availability"]:
# Convert to calendar tool call format
tool_calls.append(ToolCallRequest(
id=f"call_{len(tool_calls)}",
name="calendar",
arguments=tool_obj # Pass the whole object as arguments
))
# Remove the tool call from content
content = content[:json_start] + content[json_end:].strip()
start_pos = json_start # Stay at same position since we removed text
continue
# Handle standard format: {"name": "...", "parameters": {...}}
# Note: This list should match tools registered in AgentLoop._register_default_tools()
valid_tools = [
# File tools

View File

@ -46,6 +46,7 @@ dependencies = [
"google-api-python-client>=2.0.0",
"google-auth-httplib2>=0.2.0",
"google-auth-oauthlib>=1.0.0",
"pytz>=2024.1",
]
[project.optional-dependencies]