langchain-trigger-server 0.2.6rc8__py3-none-any.whl → 0.2.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of langchain-trigger-server might be problematic. Click here for more details.

@@ -1,13 +1,14 @@
1
1
  """Supabase implementation of trigger database interface."""
2
2
 
3
- import os
4
- import logging
5
- from typing import List, Optional, Dict, Any
6
- from supabase import create_client, Client
7
3
  import base64
8
4
  import hashlib
9
- from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
5
+ import logging
6
+ import os
7
+ from typing import Any
8
+
10
9
  from cryptography.hazmat.backends import default_backend
10
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
11
+ from supabase import create_client
11
12
 
12
13
  from .interface import TriggerDatabaseInterface
13
14
 
@@ -16,67 +17,73 @@ logger = logging.getLogger(__name__)
16
17
 
17
18
  class SupabaseTriggerDatabase(TriggerDatabaseInterface):
18
19
  """Supabase implementation of trigger database operations."""
19
-
20
+
20
21
  def __init__(self, supabase_url: str = None, supabase_key: str = None):
21
22
  self.supabase_url = supabase_url or os.getenv("SUPABASE_URL")
22
23
  self.supabase_key = supabase_key or os.getenv("SUPABASE_KEY")
23
-
24
+
24
25
  if not self.supabase_url or not self.supabase_key:
25
- raise ValueError("SUPABASE_URL and SUPABASE_KEY environment variables are required")
26
-
26
+ raise ValueError(
27
+ "SUPABASE_URL and SUPABASE_KEY environment variables are required"
28
+ )
29
+
27
30
  self.client = create_client(self.supabase_url, self.supabase_key)
28
-
31
+
29
32
  # Get encryption key for API key decryption - required
30
33
  self.encryption_key = os.getenv("SECRETS_ENCRYPTION_KEY")
31
34
  if not self.encryption_key:
32
35
  raise ValueError("SECRETS_ENCRYPTION_KEY environment variable is required")
33
-
36
+
34
37
  logger.info("Initialized SupabaseTriggerDatabase")
35
-
38
+
36
39
  def _decrypt_secret(self, encrypted_secret: str) -> str:
37
40
  """Decrypt an encrypted secret using AES-256-GCM to match OAP Node.js implementation."""
38
41
  try:
39
42
  # Decode the base64 encoded encrypted data
40
43
  combined = base64.b64decode(encrypted_secret)
41
-
44
+
42
45
  # Constants from Node.js implementation
43
46
  IV_LENGTH = 12 # 96 bits
44
47
  TAG_LENGTH = 16 # 128 bits
45
-
48
+
46
49
  # Minimum length check
47
50
  if len(combined) < IV_LENGTH + TAG_LENGTH + 1:
48
- raise ValueError("Invalid encrypted secret format: too short or malformed")
49
-
51
+ raise ValueError(
52
+ "Invalid encrypted secret format: too short or malformed"
53
+ )
54
+
50
55
  # Extract IV, encrypted data, and auth tag
51
56
  iv = combined[:IV_LENGTH]
52
57
  tag = combined[-TAG_LENGTH:]
53
58
  encrypted_data = combined[IV_LENGTH:-TAG_LENGTH]
54
-
59
+
55
60
  # Derive key using SHA-256 hash (same as Node.js deriveKey function)
56
61
  key = hashlib.sha256(self.encryption_key.encode()).digest()
57
-
62
+
58
63
  # Create AES-GCM cipher
59
- cipher = Cipher(algorithms.AES(key), modes.GCM(iv, tag), backend=default_backend())
64
+ cipher = Cipher(
65
+ algorithms.AES(key), modes.GCM(iv, tag), backend=default_backend()
66
+ )
60
67
  decryptor = cipher.decryptor()
61
-
68
+
62
69
  # Decrypt the data
63
70
  decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
64
-
65
- return decrypted_data.decode('utf-8')
71
+
72
+ return decrypted_data.decode("utf-8")
66
73
  except Exception as e:
67
74
  logger.error(f"Error decrypting secret: {e}")
68
75
  raise ValueError("Failed to decrypt API key")
69
-
76
+
70
77
  # ========== Trigger Templates ==========
71
-
78
+
72
79
  async def create_trigger_template(
73
- self,
80
+ self,
74
81
  id: str,
75
82
  provider: str,
76
- name: str,
83
+ name: str,
77
84
  description: str = None,
78
- registration_schema: Dict = None
79
- ) -> Optional[Dict[str, Any]]:
85
+ registration_schema: dict = None,
86
+ ) -> dict[str, Any] | None:
80
87
  """Create a new trigger template."""
81
88
  try:
82
89
  data = {
@@ -84,17 +91,17 @@ class SupabaseTriggerDatabase(TriggerDatabaseInterface):
84
91
  "provider": provider,
85
92
  "name": name,
86
93
  "description": description,
87
- "registration_schema": registration_schema or {}
94
+ "registration_schema": registration_schema or {},
88
95
  }
89
-
96
+
90
97
  response = self.client.table("trigger_templates").insert(data).execute()
91
98
  return response.data[0] if response.data else None
92
-
99
+
93
100
  except Exception as e:
94
101
  logger.error(f"Error creating trigger template: {e}")
95
102
  return None
96
-
97
- async def get_trigger_templates(self) -> List[Dict[str, Any]]:
103
+
104
+ async def get_trigger_templates(self) -> list[dict[str, Any]]:
98
105
  """Get all available trigger templates."""
99
106
  try:
100
107
  response = self.client.table("trigger_templates").select("*").execute()
@@ -102,29 +109,34 @@ class SupabaseTriggerDatabase(TriggerDatabaseInterface):
102
109
  except Exception as e:
103
110
  logger.error(f"Error getting trigger templates: {e}")
104
111
  return []
105
-
106
- async def get_trigger_template(self, id: str) -> Optional[Dict[str, Any]]:
112
+
113
+ async def get_trigger_template(self, id: str) -> dict[str, Any] | None:
107
114
  """Get a specific trigger template by ID."""
108
115
  try:
109
- response = self.client.table("trigger_templates").select("*").eq("id", id).single().execute()
116
+ response = (
117
+ self.client.table("trigger_templates")
118
+ .select("*")
119
+ .eq("id", id)
120
+ .single()
121
+ .execute()
122
+ )
110
123
  return response.data if response.data else None
111
124
  except Exception as e:
112
125
  # Don't log as error if template just doesn't exist (expected on first startup)
113
- if "no rows returned" in str(e).lower() or "multiple (or no) rows returned" in str(e).lower():
126
+ if (
127
+ "no rows returned" in str(e).lower()
128
+ or "multiple (or no) rows returned" in str(e).lower()
129
+ ):
114
130
  logger.debug(f"Trigger template {id} not found in database")
115
131
  else:
116
132
  logger.error(f"Error getting trigger template {id}: {e}")
117
133
  return None
118
-
134
+
119
135
  # ========== Trigger Registrations ==========
120
-
136
+
121
137
  async def create_trigger_registration(
122
- self,
123
- user_id: str,
124
- template_id: str,
125
- resource: Dict,
126
- metadata: Dict = None
127
- ) -> Optional[Dict[str, Any]]:
138
+ self, user_id: str, template_id: str, resource: dict, metadata: dict = None
139
+ ) -> dict[str, Any] | None:
128
140
  """Create a new trigger registration for a user."""
129
141
  try:
130
142
  # Verify template exists
@@ -132,78 +144,101 @@ class SupabaseTriggerDatabase(TriggerDatabaseInterface):
132
144
  if not template:
133
145
  logger.error(f"Template not found for ID: {template_id}")
134
146
  return None
135
-
147
+
136
148
  data = {
137
149
  "user_id": user_id,
138
150
  "template_id": template_id,
139
151
  "resource": resource,
140
152
  "metadata": metadata or {},
141
- "status": "active"
153
+ "status": "active",
142
154
  }
143
-
155
+
144
156
  response = self.client.table("trigger_registrations").insert(data).execute()
145
157
  return response.data[0] if response.data else None
146
-
158
+
147
159
  except Exception as e:
148
160
  logger.exception(f"Error creating trigger registration: {e}")
149
161
  return None
150
-
151
- async def get_user_trigger_registrations(self, user_id: str) -> List[Dict[str, Any]]:
162
+
163
+ async def get_user_trigger_registrations(
164
+ self, user_id: str
165
+ ) -> list[dict[str, Any]]:
152
166
  """Get all trigger registrations for a user."""
153
167
  try:
154
- response = self.client.table("trigger_registrations").select("""
168
+ response = (
169
+ self.client.table("trigger_registrations")
170
+ .select("""
155
171
  *,
156
172
  trigger_templates(id, name, description)
157
- """).eq("user_id", user_id).order("created_at", desc=True).execute()
158
-
173
+ """)
174
+ .eq("user_id", user_id)
175
+ .order("created_at", desc=True)
176
+ .execute()
177
+ )
178
+
159
179
  return response.data or []
160
180
  except Exception as e:
161
181
  logger.error(f"Error getting user trigger registrations: {e}")
162
182
  return []
163
183
 
164
- async def get_user_trigger_registrations_with_agents(self, user_id: str) -> List[Dict[str, Any]]:
184
+ async def get_user_trigger_registrations_with_agents(
185
+ self, user_id: str
186
+ ) -> list[dict[str, Any]]:
165
187
  """Get all trigger registrations for a user with linked agents in a single query."""
166
188
  try:
167
- response = self.client.table("trigger_registrations").select("""
189
+ response = (
190
+ self.client.table("trigger_registrations")
191
+ .select("""
168
192
  *,
169
193
  trigger_templates(id, name, description),
170
194
  agent_trigger_links(agent_id)
171
- """).eq("user_id", user_id).order("created_at", desc=True).execute()
172
-
195
+ """)
196
+ .eq("user_id", user_id)
197
+ .order("created_at", desc=True)
198
+ .execute()
199
+ )
200
+
173
201
  # Process the results to extract linked agent IDs
174
202
  if response.data:
175
203
  for registration in response.data:
176
204
  # Extract agent IDs from the agent_trigger_links
177
205
  agent_links = registration.get("agent_trigger_links", [])
178
- linked_agent_ids = [link.get("agent_id") for link in agent_links if link.get("agent_id")]
206
+ linked_agent_ids = [
207
+ link.get("agent_id")
208
+ for link in agent_links
209
+ if link.get("agent_id")
210
+ ]
179
211
  registration["linked_agent_ids"] = linked_agent_ids
180
-
212
+
181
213
  # Clean up the raw agent_trigger_links data as it's no longer needed
182
214
  registration.pop("agent_trigger_links", None)
183
-
215
+
184
216
  return response.data or []
185
217
  except Exception as e:
186
218
  logger.error(f"Error getting user trigger registrations with agents: {e}")
187
219
  return []
188
-
189
- async def get_trigger_registration(self, registration_id: str, user_id: str = None) -> Optional[Dict[str, Any]]:
220
+
221
+ async def get_trigger_registration(
222
+ self, registration_id: str, user_id: str = None
223
+ ) -> dict[str, Any] | None:
190
224
  """Get a specific trigger registration."""
191
225
  try:
192
- query = self.client.table("trigger_registrations").select("*").eq("id", registration_id)
226
+ query = (
227
+ self.client.table("trigger_registrations")
228
+ .select("*")
229
+ .eq("id", registration_id)
230
+ )
193
231
  if user_id:
194
232
  query = query.eq("user_id", user_id)
195
-
233
+
196
234
  response = query.single().execute()
197
235
  return response.data if response.data else None
198
236
  except Exception as e:
199
237
  logger.error(f"Error getting trigger registration {registration_id}: {e}")
200
238
  return None
201
-
239
+
202
240
  async def update_trigger_metadata(
203
- self,
204
- registration_id: str,
205
- metadata_updates: Dict,
206
- user_id: str = None
241
+ self, registration_id: str, metadata_updates: dict, user_id: str = None
207
242
  ) -> bool:
208
243
  """Update metadata for a trigger registration."""
209
244
  try:
@@ -211,117 +246,125 @@ class SupabaseTriggerDatabase(TriggerDatabaseInterface):
211
246
  current = await self.get_trigger_registration(registration_id, user_id)
212
247
  if not current:
213
248
  return False
214
-
249
+
215
250
  # Merge existing metadata with updates
216
251
  current_metadata = current.get("metadata", {})
217
252
  updated_metadata = {**current_metadata, **metadata_updates}
218
-
219
- query = self.client.table("trigger_registrations").update({
220
- "metadata": updated_metadata,
221
- "updated_at": "NOW()"
222
- }).eq("id", registration_id)
223
-
253
+
254
+ query = (
255
+ self.client.table("trigger_registrations")
256
+ .update({"metadata": updated_metadata, "updated_at": "NOW()"})
257
+ .eq("id", registration_id)
258
+ )
259
+
224
260
  if user_id:
225
261
  query = query.eq("user_id", user_id)
226
-
262
+
227
263
  response = query.execute()
228
264
  return bool(response.data)
229
-
265
+
230
266
  except Exception as e:
231
267
  logger.error(f"Error updating trigger metadata: {e}")
232
268
  return False
233
-
269
+
234
270
  async def delete_trigger_registration(
235
- self,
236
- registration_id: str,
237
- user_id: str = None
271
+ self, registration_id: str, user_id: str = None
238
272
  ) -> bool:
239
273
  """Delete a trigger registration."""
240
274
  try:
241
- query = self.client.table("trigger_registrations").delete().eq("id", registration_id)
275
+ query = (
276
+ self.client.table("trigger_registrations")
277
+ .delete()
278
+ .eq("id", registration_id)
279
+ )
242
280
  if user_id:
243
281
  query = query.eq("user_id", user_id)
244
-
245
- response = query.execute()
282
+
283
+ query.execute()
246
284
  return True # Delete operations don't return data
247
-
285
+
248
286
  except Exception as e:
249
287
  logger.error(f"Error deleting trigger registration: {e}")
250
288
  return False
251
-
289
+
252
290
  async def find_registration_by_resource(
253
- self,
254
- template_id: str,
255
- resource_data: Dict[str, Any]
256
- ) -> Optional[Dict[str, Any]]:
291
+ self, template_id: str, resource_data: dict[str, Any]
292
+ ) -> dict[str, Any] | None:
257
293
  """Find trigger registration by matching resource data."""
258
294
  try:
259
295
  # Build query to match against trigger_registrations with template_id filter
260
- query = self.client.table("trigger_registrations").select(
261
- "*, trigger_templates(id, name, description)"
262
- ).eq("template_id", template_id)
263
-
296
+ query = (
297
+ self.client.table("trigger_registrations")
298
+ .select("*, trigger_templates(id, name, description)")
299
+ .eq("template_id", template_id)
300
+ )
301
+
264
302
  # Add resource field matches
265
303
  for field, value in resource_data.items():
266
304
  query = query.eq(f"resource->>{field}", value)
267
-
305
+
268
306
  response = query.execute()
269
-
307
+
270
308
  if response.data:
271
309
  return response.data[0] # Return first match
272
310
  return None
273
-
311
+
274
312
  except Exception as e:
275
313
  logger.error(f"Error finding registration by resource: {e}")
276
314
  return None
277
-
315
+
278
316
  async def find_user_registration_by_resource(
279
- self,
280
- user_id: str,
281
- template_id: str,
282
- resource_data: Dict[str, Any]
283
- ) -> Optional[Dict[str, Any]]:
317
+ self, user_id: str, template_id: str, resource_data: dict[str, Any]
318
+ ) -> dict[str, Any] | None:
284
319
  """Find trigger registration by matching resource data for a specific user."""
285
320
  try:
286
321
  # Build query to match against trigger_registrations with template_id and user_id filter
287
- query = self.client.table("trigger_registrations").select(
288
- "*, trigger_templates(id, name, description)"
289
- ).eq("template_id", template_id).eq("user_id", user_id)
290
-
322
+ query = (
323
+ self.client.table("trigger_registrations")
324
+ .select("*, trigger_templates(id, name, description)")
325
+ .eq("template_id", template_id)
326
+ .eq("user_id", user_id)
327
+ )
328
+
291
329
  # Add resource field matches
292
330
  for field, value in resource_data.items():
293
331
  query = query.eq(f"resource->>{field}", value)
294
-
332
+
295
333
  response = query.execute()
296
-
334
+
297
335
  if response.data:
298
336
  return response.data[0] # Return first match
299
337
  return None
300
-
338
+
301
339
  except Exception as e:
302
340
  logger.error(f"Error finding user registration by resource: {e}")
303
341
  return None
304
-
305
- async def get_all_registrations(self, template_id: str) -> List[Dict[str, Any]]:
342
+
343
+ async def get_all_registrations(self, template_id: str) -> list[dict[str, Any]]:
306
344
  """Get all registrations for a specific trigger template."""
307
345
  try:
308
- response = self.client.table("trigger_registrations").select(
309
- "*, trigger_templates(id, name, description)"
310
- ).eq("template_id", template_id).execute()
311
-
346
+ response = (
347
+ self.client.table("trigger_registrations")
348
+ .select("*, trigger_templates(id, name, description)")
349
+ .eq("template_id", template_id)
350
+ .execute()
351
+ )
352
+
312
353
  return response.data or []
313
354
  except Exception as e:
314
- logger.error(f"Error getting all registrations for template {template_id}: {e}")
355
+ logger.error(
356
+ f"Error getting all registrations for template {template_id}: {e}"
357
+ )
315
358
  return []
316
-
359
+
317
360
  # ========== Agent-Trigger Links ==========
318
-
361
+
319
362
  async def link_agent_to_trigger(
320
363
  self,
321
364
  agent_id: str,
322
- registration_id: str,
365
+ registration_id: str,
323
366
  created_by: str,
324
- field_selection: Optional[Dict[str, bool]] = None
367
+ field_selection: dict[str, bool] | None = None,
325
368
  ) -> bool:
326
369
  """Link an agent to a trigger registration with optional field selection."""
327
370
  try:
@@ -329,66 +372,78 @@ class SupabaseTriggerDatabase(TriggerDatabaseInterface):
329
372
  "agent_id": agent_id,
330
373
  "registration_id": registration_id,
331
374
  "created_by": created_by,
332
- "field_selection": field_selection
375
+ "field_selection": field_selection,
333
376
  }
334
-
377
+
335
378
  response = self.client.table("agent_trigger_links").insert(data).execute()
336
379
  return bool(response.data)
337
-
380
+
338
381
  except Exception as e:
339
382
  logger.error(f"Error linking agent to trigger: {e}")
340
383
  return False
341
-
384
+
342
385
  async def unlink_agent_from_trigger(
343
- self,
344
- agent_id: str,
345
- registration_id: str
386
+ self, agent_id: str, registration_id: str
346
387
  ) -> bool:
347
388
  """Unlink an agent from a trigger registration."""
348
389
  try:
349
- response = self.client.table("agent_trigger_links").delete().eq(
350
- "agent_id", agent_id
351
- ).eq("registration_id", registration_id).execute()
352
-
390
+ (
391
+ self.client.table("agent_trigger_links")
392
+ .delete()
393
+ .eq("agent_id", agent_id)
394
+ .eq("registration_id", registration_id)
395
+ .execute()
396
+ )
397
+
353
398
  return True # Delete operations don't return data
354
-
399
+
355
400
  except Exception as e:
356
401
  logger.error(f"Error unlinking agent from trigger: {e}")
357
402
  return False
358
-
359
- async def get_agents_for_trigger(self, registration_id: str) -> List[Dict[str, Any]]:
403
+
404
+ async def get_agents_for_trigger(
405
+ self, registration_id: str
406
+ ) -> list[dict[str, Any]]:
360
407
  """Get all agent links for a trigger registration with field_selection."""
361
408
  try:
362
- response = self.client.table("agent_trigger_links").select("agent_id, field_selection").eq(
363
- "registration_id", registration_id
364
- ).execute()
365
-
409
+ response = (
410
+ self.client.table("agent_trigger_links")
411
+ .select("agent_id, field_selection")
412
+ .eq("registration_id", registration_id)
413
+ .execute()
414
+ )
415
+
366
416
  return response.data or []
367
-
417
+
368
418
  except Exception as e:
369
419
  logger.error(f"Error getting agents for trigger: {e}")
370
420
  return []
371
-
372
- async def get_triggers_for_agent(self, agent_id: str) -> List[Dict[str, Any]]:
421
+
422
+ async def get_triggers_for_agent(self, agent_id: str) -> list[dict[str, Any]]:
373
423
  """Get all trigger registrations linked to an agent."""
374
424
  try:
375
- response = self.client.table("agent_trigger_links").select("""
425
+ response = (
426
+ self.client.table("agent_trigger_links")
427
+ .select("""
376
428
  registration_id,
377
429
  trigger_registrations(
378
430
  *,
379
431
  trigger_templates(id, name, description)
380
432
  )
381
- """).eq("agent_id", agent_id).execute()
382
-
433
+ """)
434
+ .eq("agent_id", agent_id)
435
+ .execute()
436
+ )
437
+
383
438
  return [row["trigger_registrations"] for row in response.data or []]
384
-
439
+
385
440
  except Exception as e:
386
441
  logger.error(f"Error getting triggers for agent: {e}")
387
442
  return []
388
-
443
+
389
444
  # ========== Helper Methods ==========
390
-
391
- async def get_user_from_token(self, token: str) -> Optional[str]:
445
+
446
+ async def get_user_from_token(self, token: str) -> str | None:
392
447
  """Extract user ID from JWT token via Supabase auth."""
393
448
  try:
394
449
  client = self._create_user_client(token)
@@ -397,17 +452,20 @@ class SupabaseTriggerDatabase(TriggerDatabaseInterface):
397
452
  except Exception as e:
398
453
  logger.error(f"Error getting user from token: {e}")
399
454
  return None
400
-
401
- async def get_user_by_email(self, email: str) -> Optional[str]:
455
+
456
+ async def get_user_by_email(self, email: str) -> str | None:
402
457
  """Get user ID by email from trigger registrations."""
403
458
  try:
404
- response = self.client.table("trigger_registrations").select("user_id").eq(
405
- "resource->>email", email
406
- ).limit(1).execute()
407
-
459
+ response = (
460
+ self.client.table("trigger_registrations")
461
+ .select("user_id")
462
+ .eq("resource->>email", email)
463
+ .limit(1)
464
+ .execute()
465
+ )
466
+
408
467
  return response.data[0]["user_id"] if response.data else None
409
-
468
+
410
469
  except Exception as e:
411
470
  logger.error(f"Error getting user by email: {e}")
412
471
  return None
413
-