-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtools.py
More file actions
231 lines (195 loc) · 7.91 KB
/
tools.py
File metadata and controls
231 lines (195 loc) · 7.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
"""Hermes tools for nostrcalendar — in-memory enclave, singleton per session."""
from __future__ import annotations
import time
from typing import Any, Optional
from nostrcalendar import (
AvailabilityRule,
CalendarEnclave,
CalendarEvent,
DayOfWeek,
TimeSlot,
compute_free_slots,
)
from nostrkey.keys import npub_to_hex
from tools.registry import tool_error, tool_result
_enclave: Optional[CalendarEnclave] = None
def _require_enclave() -> CalendarEnclave:
if _enclave is None:
raise RuntimeError("Calendar not initialized. Call calendar_init first.")
return _enclave
def _resolve_pubkey(npub_or_hex: str) -> str:
s = (npub_or_hex or "").strip()
if s.startswith("npub1"):
return npub_to_hex(s)
return s
def _day_from_str(s: str) -> DayOfWeek:
return DayOfWeek[s.upper()]
# -----------------------------
# calendar_init
# -----------------------------
CALENDAR_INIT_SCHEMA = {
"type": "function",
"function": {
"name": "calendar_init",
"description": "Create the calendar enclave for this session. Held in module memory; restart clears it.",
"parameters": {
"type": "object",
"properties": {
"relay_url": {"type": "string", "description": "wss:// relay URL for publishing/querying events. Optional — can be set later via calendar_set_availability."},
},
"required": [],
},
},
}
def handle_calendar_init(args: dict[str, Any], **kw) -> str:
global _enclave
try:
relay = args.get("relay_url") or ""
_enclave = CalendarEnclave.create(relay_url=relay)
return tool_result({"configured": _enclave.is_configured, "relay_url": relay or None})
except Exception as e:
return tool_error(f"calendar_init failed: {type(e).__name__}: {e}")
# -----------------------------
# calendar_set_availability
# -----------------------------
CALENDAR_SET_AVAILABILITY_SCHEMA = {
"type": "function",
"function": {
"name": "calendar_set_availability",
"description": "Set the working-hours rule. Slots is a map of weekday → list of {start,end} 24h time strings.",
"parameters": {
"type": "object",
"properties": {
"slots": {
"type": "object",
"description": "Map e.g. {\"MONDAY\": [{\"start\":\"09:00\",\"end\":\"17:00\"}], \"TUESDAY\": [...]}.",
},
"slot_duration_minutes": {"type": "integer", "default": 30},
"buffer_minutes": {"type": "integer", "default": 15},
"max_per_day": {"type": "integer", "default": 8},
"timezone": {"type": "string", "description": "IANA timezone, e.g. 'America/Toronto'.", "default": "UTC"},
"relay_url": {"type": "string"},
},
"required": ["slots"],
},
},
}
def handle_calendar_set_availability(args: dict[str, Any], **kw) -> str:
try:
enclave = _require_enclave()
raw = args.get("slots") or {}
slots = {
_day_from_str(day): [TimeSlot(start=w["start"], end=w["end"]) for w in windows]
for day, windows in raw.items()
}
rule = AvailabilityRule(
slots=slots,
slot_duration_minutes=int(args.get("slot_duration_minutes", 30)),
buffer_minutes=int(args.get("buffer_minutes", 15)),
max_per_day=int(args.get("max_per_day", 8)),
timezone=args.get("timezone") or "UTC",
)
relay = args.get("relay_url") or (enclave.relay_url if enclave.is_configured else None)
if relay:
enclave.configure(rule=rule, relay_url=relay)
return tool_result({"days_configured": [d.name for d in slots.keys()], "timezone": rule.timezone})
except Exception as e:
return tool_error(f"calendar_set_availability failed: {type(e).__name__}: {e}")
# -----------------------------
# calendar_add_event
# -----------------------------
CALENDAR_ADD_EVENT_SCHEMA = {
"type": "function",
"function": {
"name": "calendar_add_event",
"description": "Add a calendar event to the enclave. Times are unix seconds.",
"parameters": {
"type": "object",
"properties": {
"d_tag": {"type": "string", "description": "Unique identifier for the event (replaceable)."},
"title": {"type": "string"},
"start": {"type": "integer", "description": "Unix seconds."},
"end": {"type": "integer", "description": "Unix seconds."},
"description": {"type": "string"},
"location": {"type": "string"},
"participants": {"type": "array", "items": {"type": "string"}, "description": "Hex pubkeys or npubs of participants."},
},
"required": ["d_tag", "title", "start", "end"],
},
},
}
def handle_calendar_add_event(args: dict[str, Any], **kw) -> str:
try:
enclave = _require_enclave()
participants = [_resolve_pubkey(p) for p in (args.get("participants") or [])]
event = CalendarEvent(
d_tag=args["d_tag"],
title=args["title"],
start=int(args["start"]),
end=int(args["end"]),
description=args.get("description") or "",
location=args.get("location") or "",
participants=participants,
)
enclave.add_event(event)
return tool_result({"added": event.d_tag, "event_count": enclave.event_count})
except Exception as e:
return tool_error(f"calendar_add_event failed: {type(e).__name__}: {e}")
# -----------------------------
# calendar_list_events
# -----------------------------
CALENDAR_LIST_EVENTS_SCHEMA = {
"type": "function",
"function": {
"name": "calendar_list_events",
"description": "Return all events currently held in the enclave.",
"parameters": {"type": "object", "properties": {}, "required": []},
},
}
def handle_calendar_list_events(args: dict[str, Any], **kw) -> str:
try:
enclave = _require_enclave()
events = [
{"d_tag": e.d_tag, "title": e.title, "start": e.start, "end": e.end,
"description": e.description, "location": e.location, "participants": list(e.participants)}
for e in enclave.events
]
return tool_result({"count": len(events), "events": events})
except Exception as e:
return tool_error(f"calendar_list_events failed: {type(e).__name__}: {e}")
# -----------------------------
# calendar_free_slots
# -----------------------------
CALENDAR_FREE_SLOTS_SCHEMA = {
"type": "function",
"function": {
"name": "calendar_free_slots",
"description": "Compute free slots in [window_start, window_end] given availability rule + booked events.",
"parameters": {
"type": "object",
"properties": {
"window_start": {"type": "integer", "description": "Unix seconds."},
"window_end": {"type": "integer", "description": "Unix seconds."},
},
"required": ["window_start", "window_end"],
},
},
}
def handle_calendar_free_slots(args: dict[str, Any], **kw) -> str:
try:
enclave = _require_enclave()
rule = enclave.rule
if rule is None:
return tool_error("availability rule not set — call calendar_set_availability first")
slots = compute_free_slots(
events=enclave.events,
rule=rule,
window_start=int(args["window_start"]),
window_end=int(args["window_end"]),
)
return tool_result({
"count": len(slots),
"slots": [{"start": s[0], "end": s[1]} for s in slots],
})
except Exception as e:
return tool_error(f"calendar_free_slots failed: {type(e).__name__}: {e}")