langchain-trigger-server 0.3.3__py3-none-any.whl → 0.3.5__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.
- {langchain_trigger_server-0.3.3.dist-info → langchain_trigger_server-0.3.5.dist-info}/METADATA +1 -2
- langchain_trigger_server-0.3.5.dist-info/RECORD +14 -0
- langchain_triggers/app.py +31 -39
- langchain_triggers/core.py +0 -6
- langchain_triggers/cron_manager.py +1 -3
- langchain_triggers/database/__init__.py +1 -12
- langchain_triggers/database/interface.py +18 -33
- langchain_trigger_server-0.3.3.dist-info/RECORD +0 -15
- langchain_triggers/database/supabase.py +0 -471
- {langchain_trigger_server-0.3.3.dist-info → langchain_trigger_server-0.3.5.dist-info}/WHEEL +0 -0
{langchain_trigger_server-0.3.3.dist-info → langchain_trigger_server-0.3.5.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langchain-trigger-server
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
4
4
|
Summary: Generic event-driven triggers framework
|
|
5
5
|
Project-URL: Homepage, https://github.com/langchain-ai/open-agent-platform
|
|
6
6
|
Project-URL: Repository, https://github.com/langchain-ai/open-agent-platform
|
|
@@ -22,7 +22,6 @@ Requires-Dist: httpx>=0.24.0
|
|
|
22
22
|
Requires-Dist: langchain-auth>=0.0.1
|
|
23
23
|
Requires-Dist: langgraph-sdk>=0.2.6
|
|
24
24
|
Requires-Dist: pydantic>=2.0.0
|
|
25
|
-
Requires-Dist: python-jose[cryptography]>=3.3.0
|
|
26
25
|
Requires-Dist: python-multipart>=0.0.6
|
|
27
26
|
Requires-Dist: supabase>=2.0.0
|
|
28
27
|
Requires-Dist: uvicorn[standard]>=0.20.0
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
langchain_triggers/__init__.py,sha256=WoW9LC_FJRs42mLWq2iuM-jjPow2Rue50q2zm56Oul0,536
|
|
2
|
+
langchain_triggers/app.py,sha256=PoyrD7whOJoiRVaCJTXrzprWIyR6gF1i-zwCks-Qscs,40349
|
|
3
|
+
langchain_triggers/core.py,sha256=q7vHsjFuLqydWt9JxAPG4SPT4Z3uoouoqtXjTZoknwk,3669
|
|
4
|
+
langchain_triggers/cron_manager.py,sha256=5cNRUE8xMGhIVBKJVJZhxGuzJTyoBepv3QmH0f9EyEg,16342
|
|
5
|
+
langchain_triggers/decorators.py,sha256=ZhWgUjbl2jUbvGSlqgThebd0tPgqXoQ02D7BcKZ9aIs,4675
|
|
6
|
+
langchain_triggers/auth/__init__.py,sha256=RtDKuBoKYuyHzLNpKr74cmALO0PhHlWO9Ho7k3CUYFE,349
|
|
7
|
+
langchain_triggers/auth/slack_hmac.py,sha256=kiwjhTXITgQvLAtEcOv8BnnWJRJcxaQ9dXkQm3JJDQ4,2948
|
|
8
|
+
langchain_triggers/database/__init__.py,sha256=8OxGLTh2VWQ-GVVFjrOOL1qdgQ9lkmjUV1racLGuk7E,135
|
|
9
|
+
langchain_triggers/database/interface.py,sha256=Wu1MOl1rugFFVcDVM4nEMgyvwW5JxUCNpS3oD6EwkCQ,3716
|
|
10
|
+
langchain_triggers/triggers/__init__.py,sha256=Uw1544gxzN4XDRn2RzpZ5EAG6EAF38ZYQtVvlciEsMs,146
|
|
11
|
+
langchain_triggers/triggers/cron_trigger.py,sha256=YTscULtB8JesnB24IWZl0LDMkf-A9Y6dksAk7GLFJiQ,3355
|
|
12
|
+
langchain_trigger_server-0.3.5.dist-info/METADATA,sha256=Ku7_dTuqu8Ab2KbwabNO9QTlWiQprwgaReuD8ARRxPU,1438
|
|
13
|
+
langchain_trigger_server-0.3.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
+
langchain_trigger_server-0.3.5.dist-info/RECORD,,
|
langchain_triggers/app.py
CHANGED
|
@@ -23,7 +23,7 @@ from .auth.slack_hmac import (
|
|
|
23
23
|
)
|
|
24
24
|
from .core import TriggerType
|
|
25
25
|
from .cron_manager import CronTriggerManager
|
|
26
|
-
from .database import TriggerDatabaseInterface
|
|
26
|
+
from .database import TriggerDatabaseInterface
|
|
27
27
|
from .decorators import TriggerTemplate
|
|
28
28
|
|
|
29
29
|
logger = logging.getLogger(__name__)
|
|
@@ -68,17 +68,20 @@ class AuthenticationMiddleware(BaseHTTPMiddleware):
|
|
|
68
68
|
return await call_next(request)
|
|
69
69
|
|
|
70
70
|
try:
|
|
71
|
-
# Run mandatory custom authentication
|
|
72
71
|
identity = await self.auth_handler({}, dict(request.headers))
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
if (
|
|
73
|
+
not identity
|
|
74
|
+
or not identity.get("identity")
|
|
75
|
+
or not identity.get("tenant_id")
|
|
76
|
+
):
|
|
77
|
+
logger.error(
|
|
78
|
+
f"Authentication failed: missing required fields (identity={bool(identity.get('identity') if identity else None)}, tenant_id={bool(identity.get('tenant_id') if identity else None)})"
|
|
79
|
+
)
|
|
75
80
|
return Response(
|
|
76
|
-
content='{"detail": "Authentication required"}',
|
|
81
|
+
content='{"detail": "Authentication required - identity and tenant_id must be provided"}',
|
|
77
82
|
status_code=401,
|
|
78
83
|
media_type="application/json",
|
|
79
84
|
)
|
|
80
|
-
|
|
81
|
-
# Store identity in request state for endpoints to access
|
|
82
85
|
request.state.current_user = identity
|
|
83
86
|
|
|
84
87
|
except Exception as e:
|
|
@@ -105,9 +108,7 @@ class TriggerServer:
|
|
|
105
108
|
def __init__(
|
|
106
109
|
self,
|
|
107
110
|
auth_handler: Callable,
|
|
108
|
-
database: TriggerDatabaseInterface
|
|
109
|
-
database_type: str | None = "supabase",
|
|
110
|
-
**database_kwargs: Any,
|
|
111
|
+
database: TriggerDatabaseInterface,
|
|
111
112
|
):
|
|
112
113
|
# Configure uvicorn logging to use consistent formatting
|
|
113
114
|
self._configure_uvicorn_logging()
|
|
@@ -117,15 +118,7 @@ class TriggerServer:
|
|
|
117
118
|
description="Event-driven triggers framework",
|
|
118
119
|
version="0.1.0",
|
|
119
120
|
)
|
|
120
|
-
|
|
121
|
-
# Configure database: allow either instance injection or factory creation
|
|
122
|
-
# Defaults to Supabase for backward compatibility
|
|
123
|
-
if database and database_type != "supabase":
|
|
124
|
-
raise ValueError("Provide either 'database' or 'database_type', not both")
|
|
125
|
-
if database is not None:
|
|
126
|
-
self.database = database
|
|
127
|
-
else:
|
|
128
|
-
self.database = create_database(database_type, **database_kwargs)
|
|
121
|
+
self.database = database
|
|
129
122
|
self.auth_handler = auth_handler
|
|
130
123
|
|
|
131
124
|
# LangGraph configuration
|
|
@@ -286,14 +279,15 @@ class TriggerServer:
|
|
|
286
279
|
async def api_list_registrations(
|
|
287
280
|
current_user: dict[str, Any] = Depends(get_current_user),
|
|
288
281
|
) -> dict[str, Any]:
|
|
289
|
-
"""List user's trigger registrations (user-scoped)."""
|
|
282
|
+
"""List user's trigger registrations (user and tenant-scoped)."""
|
|
290
283
|
try:
|
|
291
284
|
user_id = current_user["identity"]
|
|
285
|
+
tenant_id = current_user["tenant_id"]
|
|
292
286
|
|
|
293
287
|
# Get user's trigger registrations with linked agents in a single query
|
|
294
288
|
user_registrations = (
|
|
295
289
|
await self.database.get_user_trigger_registrations_with_agents(
|
|
296
|
-
user_id
|
|
290
|
+
user_id, tenant_id
|
|
297
291
|
)
|
|
298
292
|
)
|
|
299
293
|
|
|
@@ -329,6 +323,7 @@ class TriggerServer:
|
|
|
329
323
|
logger.info(f"Registration payload received: {payload}")
|
|
330
324
|
|
|
331
325
|
user_id = current_user["identity"]
|
|
326
|
+
tenant_id = current_user["tenant_id"]
|
|
332
327
|
trigger_id = payload.get("type")
|
|
333
328
|
if not trigger_id:
|
|
334
329
|
raise HTTPException(
|
|
@@ -340,7 +335,6 @@ class TriggerServer:
|
|
|
340
335
|
raise HTTPException(
|
|
341
336
|
status_code=400, detail=f"Unknown trigger type: {trigger_id}"
|
|
342
337
|
)
|
|
343
|
-
client_metadata = payload.pop("metadata", None)
|
|
344
338
|
|
|
345
339
|
# Parse payload into registration model first
|
|
346
340
|
try:
|
|
@@ -350,11 +344,12 @@ class TriggerServer:
|
|
|
350
344
|
status_code=400, detail=f"Invalid payload for trigger: {str(e)}"
|
|
351
345
|
)
|
|
352
346
|
|
|
353
|
-
# Check for duplicate registration based on resource data within this user's scope
|
|
347
|
+
# Check for duplicate registration based on resource data within this user's tenant scope
|
|
354
348
|
resource_dict = registration_instance.model_dump()
|
|
355
349
|
existing_registration = (
|
|
356
350
|
await self.database.find_user_registration_by_resource(
|
|
357
351
|
user_id=user_id,
|
|
352
|
+
tenant_id=tenant_id,
|
|
358
353
|
template_id=trigger.id,
|
|
359
354
|
resource_data=resource_dict,
|
|
360
355
|
)
|
|
@@ -386,16 +381,12 @@ class TriggerServer:
|
|
|
386
381
|
|
|
387
382
|
resource_dict = registration_instance.model_dump()
|
|
388
383
|
|
|
389
|
-
merged_metadata = {}
|
|
390
|
-
if client_metadata:
|
|
391
|
-
merged_metadata["client_metadata"] = client_metadata
|
|
392
|
-
merged_metadata.update(result.metadata)
|
|
393
|
-
|
|
394
384
|
registration = await self.database.create_trigger_registration(
|
|
395
385
|
user_id=user_id,
|
|
386
|
+
tenant_id=tenant_id,
|
|
396
387
|
template_id=trigger.id,
|
|
397
388
|
resource=resource_dict,
|
|
398
|
-
metadata=
|
|
389
|
+
metadata=result.metadata,
|
|
399
390
|
)
|
|
400
391
|
|
|
401
392
|
if not registration:
|
|
@@ -427,8 +418,9 @@ class TriggerServer:
|
|
|
427
418
|
"""Delete a trigger registration."""
|
|
428
419
|
try:
|
|
429
420
|
user_id = current_user["identity"]
|
|
421
|
+
tenant_id = current_user["tenant_id"]
|
|
430
422
|
success = await self.database.delete_trigger_registration(
|
|
431
|
-
registration_id, user_id
|
|
423
|
+
registration_id, user_id, tenant_id
|
|
432
424
|
)
|
|
433
425
|
if not success:
|
|
434
426
|
raise HTTPException(
|
|
@@ -450,10 +442,11 @@ class TriggerServer:
|
|
|
450
442
|
"""List agents linked to this registration."""
|
|
451
443
|
try:
|
|
452
444
|
user_id = current_user["identity"]
|
|
445
|
+
tenant_id = current_user["tenant_id"]
|
|
453
446
|
|
|
454
447
|
# Get the specific trigger registration
|
|
455
448
|
trigger = await self.database.get_trigger_registration(
|
|
456
|
-
registration_id, user_id
|
|
449
|
+
registration_id, user_id, tenant_id
|
|
457
450
|
)
|
|
458
451
|
if not trigger:
|
|
459
452
|
raise HTTPException(
|
|
@@ -490,10 +483,11 @@ class TriggerServer:
|
|
|
490
483
|
field_selection = None
|
|
491
484
|
|
|
492
485
|
user_id = current_user["identity"]
|
|
486
|
+
tenant_id = current_user["tenant_id"]
|
|
493
487
|
|
|
494
488
|
# Verify the trigger registration exists and belongs to the user
|
|
495
489
|
registration = await self.database.get_trigger_registration(
|
|
496
|
-
registration_id, user_id
|
|
490
|
+
registration_id, user_id, tenant_id
|
|
497
491
|
)
|
|
498
492
|
if not registration:
|
|
499
493
|
raise HTTPException(
|
|
@@ -537,10 +531,11 @@ class TriggerServer:
|
|
|
537
531
|
"""Remove an agent from a trigger registration."""
|
|
538
532
|
try:
|
|
539
533
|
user_id = current_user["identity"]
|
|
534
|
+
tenant_id = current_user["tenant_id"]
|
|
540
535
|
|
|
541
536
|
# Verify the trigger registration exists and belongs to the user
|
|
542
537
|
registration = await self.database.get_trigger_registration(
|
|
543
|
-
registration_id, user_id
|
|
538
|
+
registration_id, user_id, tenant_id
|
|
544
539
|
)
|
|
545
540
|
if not registration:
|
|
546
541
|
raise HTTPException(
|
|
@@ -578,10 +573,11 @@ class TriggerServer:
|
|
|
578
573
|
"""Manually execute a cron trigger registration immediately."""
|
|
579
574
|
try:
|
|
580
575
|
user_id = current_user["identity"]
|
|
576
|
+
tenant_id = current_user["tenant_id"]
|
|
581
577
|
|
|
582
578
|
# Verify the trigger registration exists and belongs to the user
|
|
583
579
|
registration = await self.database.get_trigger_registration(
|
|
584
|
-
registration_id, user_id
|
|
580
|
+
registration_id, user_id, tenant_id
|
|
585
581
|
)
|
|
586
582
|
if not registration:
|
|
587
583
|
raise HTTPException(
|
|
@@ -751,11 +747,7 @@ class TriggerServer:
|
|
|
751
747
|
# Ensure agent_id and user_id are strings for JSON serialization
|
|
752
748
|
agent_id_str = str(agent_id)
|
|
753
749
|
user_id_str = str(result.registration["user_id"])
|
|
754
|
-
tenant_id_str = str(
|
|
755
|
-
result.registration.get("metadata", {})
|
|
756
|
-
.get("client_metadata", {})
|
|
757
|
-
.get("tenant_id")
|
|
758
|
-
)
|
|
750
|
+
tenant_id_str = str(result.registration["tenant_id"])
|
|
759
751
|
|
|
760
752
|
agent_input = {"messages": [{"role": "human", "content": message}]}
|
|
761
753
|
|
langchain_triggers/core.py
CHANGED
|
@@ -97,12 +97,6 @@ class TriggerRegistrationResult(BaseModel):
|
|
|
97
97
|
if self.create_registration and not self.metadata:
|
|
98
98
|
self.metadata = {} # Allow empty metadata for create_registration=True
|
|
99
99
|
|
|
100
|
-
if "client_metadata" in self.metadata:
|
|
101
|
-
raise ValueError(
|
|
102
|
-
"The 'client_metadata' key is reserved for client-provided metadata. "
|
|
103
|
-
"Registration handlers must not use this key in their metadata."
|
|
104
|
-
)
|
|
105
|
-
|
|
106
100
|
if not self.create_registration and (
|
|
107
101
|
not self.response_body or not self.status_code
|
|
108
102
|
):
|
|
@@ -285,9 +285,7 @@ class CronTriggerManager:
|
|
|
285
285
|
user_id = registration["user_id"]
|
|
286
286
|
template_id = registration.get("template_id")
|
|
287
287
|
template_id = str(template_id) if template_id is not None else None
|
|
288
|
-
tenant_id = (
|
|
289
|
-
registration.get("metadata", {}).get("client_metadata", {}).get("tenant_id")
|
|
290
|
-
)
|
|
288
|
+
tenant_id = str(registration.get("tenant_id"))
|
|
291
289
|
|
|
292
290
|
# Get agent links
|
|
293
291
|
agent_links = await self.trigger_server.database.get_agents_for_trigger(
|
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
"""Database module for trigger operations."""
|
|
2
2
|
|
|
3
3
|
from .interface import TriggerDatabaseInterface
|
|
4
|
-
from .supabase import SupabaseTriggerDatabase
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
def create_database(database_type: str, **kwargs) -> TriggerDatabaseInterface:
|
|
8
|
-
"""Factory function to create database implementation."""
|
|
9
|
-
|
|
10
|
-
if database_type == "supabase":
|
|
11
|
-
return SupabaseTriggerDatabase(**kwargs)
|
|
12
|
-
else:
|
|
13
|
-
raise ValueError(f"Unknown database type: {database_type}")
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
__all__ = ["TriggerDatabaseInterface", "SupabaseTriggerDatabase", "create_database"]
|
|
5
|
+
__all__ = ["TriggerDatabaseInterface"]
|
|
@@ -35,28 +35,33 @@ class TriggerDatabaseInterface(ABC):
|
|
|
35
35
|
|
|
36
36
|
@abstractmethod
|
|
37
37
|
async def create_trigger_registration(
|
|
38
|
-
self,
|
|
38
|
+
self,
|
|
39
|
+
user_id: str,
|
|
40
|
+
tenant_id: str,
|
|
41
|
+
template_id: str,
|
|
42
|
+
resource: dict,
|
|
43
|
+
metadata: dict = None,
|
|
39
44
|
) -> dict[str, Any] | None:
|
|
40
45
|
"""Create a new trigger registration for a user."""
|
|
41
46
|
pass
|
|
42
47
|
|
|
43
48
|
@abstractmethod
|
|
44
49
|
async def get_user_trigger_registrations(
|
|
45
|
-
self, user_id: str
|
|
50
|
+
self, user_id: str, tenant_id: str
|
|
46
51
|
) -> list[dict[str, Any]]:
|
|
47
|
-
"""Get all trigger registrations for a user."""
|
|
52
|
+
"""Get all trigger registrations for a user within a tenant."""
|
|
48
53
|
pass
|
|
49
54
|
|
|
50
55
|
@abstractmethod
|
|
51
56
|
async def get_user_trigger_registrations_with_agents(
|
|
52
|
-
self, user_id: str
|
|
57
|
+
self, user_id: str, tenant_id: str
|
|
53
58
|
) -> list[dict[str, Any]]:
|
|
54
|
-
"""Get all trigger registrations for a user with linked agents in a single query."""
|
|
59
|
+
"""Get all trigger registrations for a user with linked agents in a single query within a tenant."""
|
|
55
60
|
pass
|
|
56
61
|
|
|
57
62
|
@abstractmethod
|
|
58
63
|
async def get_trigger_registration(
|
|
59
|
-
self, registration_id: str, user_id: str
|
|
64
|
+
self, registration_id: str, user_id: str, tenant_id: str
|
|
60
65
|
) -> dict[str, Any] | None:
|
|
61
66
|
"""Get a specific trigger registration."""
|
|
62
67
|
pass
|
|
@@ -70,9 +75,13 @@ class TriggerDatabaseInterface(ABC):
|
|
|
70
75
|
|
|
71
76
|
@abstractmethod
|
|
72
77
|
async def find_user_registration_by_resource(
|
|
73
|
-
self,
|
|
78
|
+
self,
|
|
79
|
+
user_id: str,
|
|
80
|
+
tenant_id: str,
|
|
81
|
+
template_id: str,
|
|
82
|
+
resource_data: dict[str, Any],
|
|
74
83
|
) -> dict[str, Any] | None:
|
|
75
|
-
"""Find trigger registration by matching resource data for a specific user."""
|
|
84
|
+
"""Find trigger registration by matching resource data for a specific user within a tenant."""
|
|
76
85
|
pass
|
|
77
86
|
|
|
78
87
|
@abstractmethod
|
|
@@ -80,16 +89,9 @@ class TriggerDatabaseInterface(ABC):
|
|
|
80
89
|
"""Get all registrations for a specific trigger template."""
|
|
81
90
|
pass
|
|
82
91
|
|
|
83
|
-
@abstractmethod
|
|
84
|
-
async def update_trigger_metadata(
|
|
85
|
-
self, registration_id: str, metadata_updates: dict, user_id: str = None
|
|
86
|
-
) -> bool:
|
|
87
|
-
"""Update metadata for a trigger registration."""
|
|
88
|
-
pass
|
|
89
|
-
|
|
90
92
|
@abstractmethod
|
|
91
93
|
async def delete_trigger_registration(
|
|
92
|
-
self, registration_id: str, user_id: str
|
|
94
|
+
self, registration_id: str, user_id: str, tenant_id: str
|
|
93
95
|
) -> bool:
|
|
94
96
|
"""Delete a trigger registration."""
|
|
95
97
|
pass
|
|
@@ -120,20 +122,3 @@ class TriggerDatabaseInterface(ABC):
|
|
|
120
122
|
) -> list[dict[str, Any]]:
|
|
121
123
|
"""Get all agent links for a trigger registration with field_selection."""
|
|
122
124
|
pass
|
|
123
|
-
|
|
124
|
-
@abstractmethod
|
|
125
|
-
async def get_triggers_for_agent(self, agent_id: str) -> list[dict[str, Any]]:
|
|
126
|
-
"""Get all trigger registrations linked to an agent."""
|
|
127
|
-
pass
|
|
128
|
-
|
|
129
|
-
# ========== Helper Methods ==========
|
|
130
|
-
|
|
131
|
-
@abstractmethod
|
|
132
|
-
async def get_user_from_token(self, token: str) -> str | None:
|
|
133
|
-
"""Extract user ID from authentication token."""
|
|
134
|
-
pass
|
|
135
|
-
|
|
136
|
-
@abstractmethod
|
|
137
|
-
async def get_user_by_email(self, email: str) -> str | None:
|
|
138
|
-
"""Get user ID by email from trigger registrations."""
|
|
139
|
-
pass
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
langchain_triggers/__init__.py,sha256=WoW9LC_FJRs42mLWq2iuM-jjPow2Rue50q2zm56Oul0,536
|
|
2
|
-
langchain_triggers/app.py,sha256=-VytRXkAPI8qm-fbMJMbQ52Qt8WC2nKBgL_8FGJaFTM,40468
|
|
3
|
-
langchain_triggers/core.py,sha256=6r_91UWt7c26TVqkDxHc2xjmaQl42g-iFHGEWuDo0gs,3929
|
|
4
|
-
langchain_triggers/cron_manager.py,sha256=PueW_z7gdkSza9J05W1VV2OxsfqvodMo7ZlAQJKMvpI,16408
|
|
5
|
-
langchain_triggers/decorators.py,sha256=ZhWgUjbl2jUbvGSlqgThebd0tPgqXoQ02D7BcKZ9aIs,4675
|
|
6
|
-
langchain_triggers/auth/__init__.py,sha256=RtDKuBoKYuyHzLNpKr74cmALO0PhHlWO9Ho7k3CUYFE,349
|
|
7
|
-
langchain_triggers/auth/slack_hmac.py,sha256=kiwjhTXITgQvLAtEcOv8BnnWJRJcxaQ9dXkQm3JJDQ4,2948
|
|
8
|
-
langchain_triggers/database/__init__.py,sha256=B1I1qmVr3U1CSf0VkjxsL4W5QGda5T7uB_CsJq6yBF4,535
|
|
9
|
-
langchain_triggers/database/interface.py,sha256=jpADOOwcBQo1ZichgiZVaOvfZqEqVVo8Ea7ATWWTSBE,4283
|
|
10
|
-
langchain_triggers/database/supabase.py,sha256=zi_75GbqRvzzlXd5EgfYof4h6vHWOLS4I1759wvY9kQ,17009
|
|
11
|
-
langchain_triggers/triggers/__init__.py,sha256=Uw1544gxzN4XDRn2RzpZ5EAG6EAF38ZYQtVvlciEsMs,146
|
|
12
|
-
langchain_triggers/triggers/cron_trigger.py,sha256=YTscULtB8JesnB24IWZl0LDMkf-A9Y6dksAk7GLFJiQ,3355
|
|
13
|
-
langchain_trigger_server-0.3.3.dist-info/METADATA,sha256=lSxdTnBzgk9FQKnXa8BIvgfsLi-36RREMkfiVgQU1c0,1486
|
|
14
|
-
langchain_trigger_server-0.3.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
15
|
-
langchain_trigger_server-0.3.3.dist-info/RECORD,,
|
|
@@ -1,471 +0,0 @@
|
|
|
1
|
-
"""Supabase implementation of trigger database interface."""
|
|
2
|
-
|
|
3
|
-
import base64
|
|
4
|
-
import hashlib
|
|
5
|
-
import logging
|
|
6
|
-
import os
|
|
7
|
-
from typing import Any
|
|
8
|
-
|
|
9
|
-
from cryptography.hazmat.backends import default_backend
|
|
10
|
-
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
11
|
-
from supabase import create_client
|
|
12
|
-
|
|
13
|
-
from .interface import TriggerDatabaseInterface
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class SupabaseTriggerDatabase(TriggerDatabaseInterface):
|
|
19
|
-
"""Supabase implementation of trigger database operations."""
|
|
20
|
-
|
|
21
|
-
def __init__(self, supabase_url: str = None, supabase_key: str = None):
|
|
22
|
-
self.supabase_url = supabase_url or os.getenv("SUPABASE_URL")
|
|
23
|
-
self.supabase_key = supabase_key or os.getenv("SUPABASE_KEY")
|
|
24
|
-
|
|
25
|
-
if not self.supabase_url or not self.supabase_key:
|
|
26
|
-
raise ValueError(
|
|
27
|
-
"SUPABASE_URL and SUPABASE_KEY environment variables are required"
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
self.client = create_client(self.supabase_url, self.supabase_key)
|
|
31
|
-
|
|
32
|
-
# Get encryption key for API key decryption - required
|
|
33
|
-
self.encryption_key = os.getenv("SECRETS_ENCRYPTION_KEY")
|
|
34
|
-
if not self.encryption_key:
|
|
35
|
-
raise ValueError("SECRETS_ENCRYPTION_KEY environment variable is required")
|
|
36
|
-
|
|
37
|
-
logger.info("Initialized SupabaseTriggerDatabase")
|
|
38
|
-
|
|
39
|
-
def _decrypt_secret(self, encrypted_secret: str) -> str:
|
|
40
|
-
"""Decrypt an encrypted secret using AES-256-GCM to match OAP Node.js implementation."""
|
|
41
|
-
try:
|
|
42
|
-
# Decode the base64 encoded encrypted data
|
|
43
|
-
combined = base64.b64decode(encrypted_secret)
|
|
44
|
-
|
|
45
|
-
# Constants from Node.js implementation
|
|
46
|
-
IV_LENGTH = 12 # 96 bits
|
|
47
|
-
TAG_LENGTH = 16 # 128 bits
|
|
48
|
-
|
|
49
|
-
# Minimum length check
|
|
50
|
-
if len(combined) < IV_LENGTH + TAG_LENGTH + 1:
|
|
51
|
-
raise ValueError(
|
|
52
|
-
"Invalid encrypted secret format: too short or malformed"
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
# Extract IV, encrypted data, and auth tag
|
|
56
|
-
iv = combined[:IV_LENGTH]
|
|
57
|
-
tag = combined[-TAG_LENGTH:]
|
|
58
|
-
encrypted_data = combined[IV_LENGTH:-TAG_LENGTH]
|
|
59
|
-
|
|
60
|
-
# Derive key using SHA-256 hash (same as Node.js deriveKey function)
|
|
61
|
-
key = hashlib.sha256(self.encryption_key.encode()).digest()
|
|
62
|
-
|
|
63
|
-
# Create AES-GCM cipher
|
|
64
|
-
cipher = Cipher(
|
|
65
|
-
algorithms.AES(key), modes.GCM(iv, tag), backend=default_backend()
|
|
66
|
-
)
|
|
67
|
-
decryptor = cipher.decryptor()
|
|
68
|
-
|
|
69
|
-
# Decrypt the data
|
|
70
|
-
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
|
|
71
|
-
|
|
72
|
-
return decrypted_data.decode("utf-8")
|
|
73
|
-
except Exception as e:
|
|
74
|
-
logger.error(f"Error decrypting secret: {e}")
|
|
75
|
-
raise ValueError("Failed to decrypt API key")
|
|
76
|
-
|
|
77
|
-
# ========== Trigger Templates ==========
|
|
78
|
-
|
|
79
|
-
async def create_trigger_template(
|
|
80
|
-
self,
|
|
81
|
-
id: str,
|
|
82
|
-
provider: str,
|
|
83
|
-
name: str,
|
|
84
|
-
description: str = None,
|
|
85
|
-
registration_schema: dict = None,
|
|
86
|
-
) -> dict[str, Any] | None:
|
|
87
|
-
"""Create a new trigger template."""
|
|
88
|
-
try:
|
|
89
|
-
data = {
|
|
90
|
-
"id": id,
|
|
91
|
-
"provider": provider,
|
|
92
|
-
"name": name,
|
|
93
|
-
"description": description,
|
|
94
|
-
"registration_schema": registration_schema or {},
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
response = self.client.table("trigger_templates").insert(data).execute()
|
|
98
|
-
return response.data[0] if response.data else None
|
|
99
|
-
|
|
100
|
-
except Exception as e:
|
|
101
|
-
logger.error(f"Error creating trigger template: {e}")
|
|
102
|
-
return None
|
|
103
|
-
|
|
104
|
-
async def get_trigger_templates(self) -> list[dict[str, Any]]:
|
|
105
|
-
"""Get all available trigger templates."""
|
|
106
|
-
try:
|
|
107
|
-
response = self.client.table("trigger_templates").select("*").execute()
|
|
108
|
-
return response.data or []
|
|
109
|
-
except Exception as e:
|
|
110
|
-
logger.error(f"Error getting trigger templates: {e}")
|
|
111
|
-
return []
|
|
112
|
-
|
|
113
|
-
async def get_trigger_template(self, id: str) -> dict[str, Any] | None:
|
|
114
|
-
"""Get a specific trigger template by ID."""
|
|
115
|
-
try:
|
|
116
|
-
response = (
|
|
117
|
-
self.client.table("trigger_templates")
|
|
118
|
-
.select("*")
|
|
119
|
-
.eq("id", id)
|
|
120
|
-
.single()
|
|
121
|
-
.execute()
|
|
122
|
-
)
|
|
123
|
-
return response.data if response.data else None
|
|
124
|
-
except Exception as e:
|
|
125
|
-
# Don't log as error if template just doesn't exist (expected on first startup)
|
|
126
|
-
if (
|
|
127
|
-
"no rows returned" in str(e).lower()
|
|
128
|
-
or "multiple (or no) rows returned" in str(e).lower()
|
|
129
|
-
):
|
|
130
|
-
logger.debug(f"Trigger template {id} not found in database")
|
|
131
|
-
else:
|
|
132
|
-
logger.error(f"Error getting trigger template {id}: {e}")
|
|
133
|
-
return None
|
|
134
|
-
|
|
135
|
-
# ========== Trigger Registrations ==========
|
|
136
|
-
|
|
137
|
-
async def create_trigger_registration(
|
|
138
|
-
self, user_id: str, template_id: str, resource: dict, metadata: dict = None
|
|
139
|
-
) -> dict[str, Any] | None:
|
|
140
|
-
"""Create a new trigger registration for a user."""
|
|
141
|
-
try:
|
|
142
|
-
# Verify template exists
|
|
143
|
-
template = await self.get_trigger_template(template_id)
|
|
144
|
-
if not template:
|
|
145
|
-
logger.error(f"Template not found for ID: {template_id}")
|
|
146
|
-
return None
|
|
147
|
-
|
|
148
|
-
data = {
|
|
149
|
-
"user_id": user_id,
|
|
150
|
-
"template_id": template_id,
|
|
151
|
-
"resource": resource,
|
|
152
|
-
"metadata": metadata or {},
|
|
153
|
-
"status": "active",
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
response = self.client.table("trigger_registrations").insert(data).execute()
|
|
157
|
-
return response.data[0] if response.data else None
|
|
158
|
-
|
|
159
|
-
except Exception as e:
|
|
160
|
-
logger.exception(f"Error creating trigger registration: {e}")
|
|
161
|
-
return None
|
|
162
|
-
|
|
163
|
-
async def get_user_trigger_registrations(
|
|
164
|
-
self, user_id: str
|
|
165
|
-
) -> list[dict[str, Any]]:
|
|
166
|
-
"""Get all trigger registrations for a user."""
|
|
167
|
-
try:
|
|
168
|
-
response = (
|
|
169
|
-
self.client.table("trigger_registrations")
|
|
170
|
-
.select("""
|
|
171
|
-
*,
|
|
172
|
-
trigger_templates(id, name, description)
|
|
173
|
-
""")
|
|
174
|
-
.eq("user_id", user_id)
|
|
175
|
-
.order("created_at", desc=True)
|
|
176
|
-
.execute()
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
return response.data or []
|
|
180
|
-
except Exception as e:
|
|
181
|
-
logger.error(f"Error getting user trigger registrations: {e}")
|
|
182
|
-
return []
|
|
183
|
-
|
|
184
|
-
async def get_user_trigger_registrations_with_agents(
|
|
185
|
-
self, user_id: str
|
|
186
|
-
) -> list[dict[str, Any]]:
|
|
187
|
-
"""Get all trigger registrations for a user with linked agents in a single query."""
|
|
188
|
-
try:
|
|
189
|
-
response = (
|
|
190
|
-
self.client.table("trigger_registrations")
|
|
191
|
-
.select("""
|
|
192
|
-
*,
|
|
193
|
-
trigger_templates(id, name, description),
|
|
194
|
-
agent_trigger_links(agent_id)
|
|
195
|
-
""")
|
|
196
|
-
.eq("user_id", user_id)
|
|
197
|
-
.order("created_at", desc=True)
|
|
198
|
-
.execute()
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
# Process the results to extract linked agent IDs
|
|
202
|
-
if response.data:
|
|
203
|
-
for registration in response.data:
|
|
204
|
-
# Extract agent IDs from the agent_trigger_links
|
|
205
|
-
agent_links = registration.get("agent_trigger_links", [])
|
|
206
|
-
linked_agent_ids = [
|
|
207
|
-
link.get("agent_id")
|
|
208
|
-
for link in agent_links
|
|
209
|
-
if link.get("agent_id")
|
|
210
|
-
]
|
|
211
|
-
registration["linked_agent_ids"] = linked_agent_ids
|
|
212
|
-
|
|
213
|
-
# Clean up the raw agent_trigger_links data as it's no longer needed
|
|
214
|
-
registration.pop("agent_trigger_links", None)
|
|
215
|
-
|
|
216
|
-
return response.data or []
|
|
217
|
-
except Exception as e:
|
|
218
|
-
logger.error(f"Error getting user trigger registrations with agents: {e}")
|
|
219
|
-
return []
|
|
220
|
-
|
|
221
|
-
async def get_trigger_registration(
|
|
222
|
-
self, registration_id: str, user_id: str = None
|
|
223
|
-
) -> dict[str, Any] | None:
|
|
224
|
-
"""Get a specific trigger registration."""
|
|
225
|
-
try:
|
|
226
|
-
query = (
|
|
227
|
-
self.client.table("trigger_registrations")
|
|
228
|
-
.select("*")
|
|
229
|
-
.eq("id", registration_id)
|
|
230
|
-
)
|
|
231
|
-
if user_id:
|
|
232
|
-
query = query.eq("user_id", user_id)
|
|
233
|
-
|
|
234
|
-
response = query.single().execute()
|
|
235
|
-
return response.data if response.data else None
|
|
236
|
-
except Exception as e:
|
|
237
|
-
logger.error(f"Error getting trigger registration {registration_id}: {e}")
|
|
238
|
-
return None
|
|
239
|
-
|
|
240
|
-
async def update_trigger_metadata(
|
|
241
|
-
self, registration_id: str, metadata_updates: dict, user_id: str = None
|
|
242
|
-
) -> bool:
|
|
243
|
-
"""Update metadata for a trigger registration."""
|
|
244
|
-
try:
|
|
245
|
-
# Get current registration to merge metadata
|
|
246
|
-
current = await self.get_trigger_registration(registration_id, user_id)
|
|
247
|
-
if not current:
|
|
248
|
-
return False
|
|
249
|
-
|
|
250
|
-
# Merge existing metadata with updates
|
|
251
|
-
current_metadata = current.get("metadata", {})
|
|
252
|
-
updated_metadata = {**current_metadata, **metadata_updates}
|
|
253
|
-
|
|
254
|
-
query = (
|
|
255
|
-
self.client.table("trigger_registrations")
|
|
256
|
-
.update({"metadata": updated_metadata, "updated_at": "NOW()"})
|
|
257
|
-
.eq("id", registration_id)
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
if user_id:
|
|
261
|
-
query = query.eq("user_id", user_id)
|
|
262
|
-
|
|
263
|
-
response = query.execute()
|
|
264
|
-
return bool(response.data)
|
|
265
|
-
|
|
266
|
-
except Exception as e:
|
|
267
|
-
logger.error(f"Error updating trigger metadata: {e}")
|
|
268
|
-
return False
|
|
269
|
-
|
|
270
|
-
async def delete_trigger_registration(
|
|
271
|
-
self, registration_id: str, user_id: str = None
|
|
272
|
-
) -> bool:
|
|
273
|
-
"""Delete a trigger registration."""
|
|
274
|
-
try:
|
|
275
|
-
query = (
|
|
276
|
-
self.client.table("trigger_registrations")
|
|
277
|
-
.delete()
|
|
278
|
-
.eq("id", registration_id)
|
|
279
|
-
)
|
|
280
|
-
if user_id:
|
|
281
|
-
query = query.eq("user_id", user_id)
|
|
282
|
-
|
|
283
|
-
query.execute()
|
|
284
|
-
return True # Delete operations don't return data
|
|
285
|
-
|
|
286
|
-
except Exception as e:
|
|
287
|
-
logger.error(f"Error deleting trigger registration: {e}")
|
|
288
|
-
return False
|
|
289
|
-
|
|
290
|
-
async def find_registration_by_resource(
|
|
291
|
-
self, template_id: str, resource_data: dict[str, Any]
|
|
292
|
-
) -> dict[str, Any] | None:
|
|
293
|
-
"""Find trigger registration by matching resource data."""
|
|
294
|
-
try:
|
|
295
|
-
# Build query to match against trigger_registrations with template_id filter
|
|
296
|
-
query = (
|
|
297
|
-
self.client.table("trigger_registrations")
|
|
298
|
-
.select("*, trigger_templates(id, name, description)")
|
|
299
|
-
.eq("template_id", template_id)
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
# Add resource field matches
|
|
303
|
-
for field, value in resource_data.items():
|
|
304
|
-
query = query.eq(f"resource->>{field}", value)
|
|
305
|
-
|
|
306
|
-
response = query.execute()
|
|
307
|
-
|
|
308
|
-
if response.data:
|
|
309
|
-
return response.data[0] # Return first match
|
|
310
|
-
return None
|
|
311
|
-
|
|
312
|
-
except Exception as e:
|
|
313
|
-
logger.error(f"Error finding registration by resource: {e}")
|
|
314
|
-
return None
|
|
315
|
-
|
|
316
|
-
async def find_user_registration_by_resource(
|
|
317
|
-
self, user_id: str, template_id: str, resource_data: dict[str, Any]
|
|
318
|
-
) -> dict[str, Any] | None:
|
|
319
|
-
"""Find trigger registration by matching resource data for a specific user."""
|
|
320
|
-
try:
|
|
321
|
-
# Build query to match against trigger_registrations with template_id and user_id filter
|
|
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
|
-
|
|
329
|
-
# Add resource field matches
|
|
330
|
-
for field, value in resource_data.items():
|
|
331
|
-
query = query.eq(f"resource->>{field}", value)
|
|
332
|
-
|
|
333
|
-
response = query.execute()
|
|
334
|
-
|
|
335
|
-
if response.data:
|
|
336
|
-
return response.data[0] # Return first match
|
|
337
|
-
return None
|
|
338
|
-
|
|
339
|
-
except Exception as e:
|
|
340
|
-
logger.error(f"Error finding user registration by resource: {e}")
|
|
341
|
-
return None
|
|
342
|
-
|
|
343
|
-
async def get_all_registrations(self, template_id: str) -> list[dict[str, Any]]:
|
|
344
|
-
"""Get all registrations for a specific trigger template."""
|
|
345
|
-
try:
|
|
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
|
-
|
|
353
|
-
return response.data or []
|
|
354
|
-
except Exception as e:
|
|
355
|
-
logger.error(
|
|
356
|
-
f"Error getting all registrations for template {template_id}: {e}"
|
|
357
|
-
)
|
|
358
|
-
return []
|
|
359
|
-
|
|
360
|
-
# ========== Agent-Trigger Links ==========
|
|
361
|
-
|
|
362
|
-
async def link_agent_to_trigger(
|
|
363
|
-
self,
|
|
364
|
-
agent_id: str,
|
|
365
|
-
registration_id: str,
|
|
366
|
-
created_by: str,
|
|
367
|
-
field_selection: dict[str, bool] | None = None,
|
|
368
|
-
) -> bool:
|
|
369
|
-
"""Link an agent to a trigger registration with optional field selection."""
|
|
370
|
-
try:
|
|
371
|
-
data = {
|
|
372
|
-
"agent_id": agent_id,
|
|
373
|
-
"registration_id": registration_id,
|
|
374
|
-
"created_by": created_by,
|
|
375
|
-
"field_selection": field_selection,
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
response = self.client.table("agent_trigger_links").insert(data).execute()
|
|
379
|
-
return bool(response.data)
|
|
380
|
-
|
|
381
|
-
except Exception as e:
|
|
382
|
-
logger.error(f"Error linking agent to trigger: {e}")
|
|
383
|
-
return False
|
|
384
|
-
|
|
385
|
-
async def unlink_agent_from_trigger(
|
|
386
|
-
self, agent_id: str, registration_id: str
|
|
387
|
-
) -> bool:
|
|
388
|
-
"""Unlink an agent from a trigger registration."""
|
|
389
|
-
try:
|
|
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
|
-
|
|
398
|
-
return True # Delete operations don't return data
|
|
399
|
-
|
|
400
|
-
except Exception as e:
|
|
401
|
-
logger.error(f"Error unlinking agent from trigger: {e}")
|
|
402
|
-
return False
|
|
403
|
-
|
|
404
|
-
async def get_agents_for_trigger(
|
|
405
|
-
self, registration_id: str
|
|
406
|
-
) -> list[dict[str, Any]]:
|
|
407
|
-
"""Get all agent links for a trigger registration with field_selection."""
|
|
408
|
-
try:
|
|
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
|
-
|
|
416
|
-
return response.data or []
|
|
417
|
-
|
|
418
|
-
except Exception as e:
|
|
419
|
-
logger.error(f"Error getting agents for trigger: {e}")
|
|
420
|
-
return []
|
|
421
|
-
|
|
422
|
-
async def get_triggers_for_agent(self, agent_id: str) -> list[dict[str, Any]]:
|
|
423
|
-
"""Get all trigger registrations linked to an agent."""
|
|
424
|
-
try:
|
|
425
|
-
response = (
|
|
426
|
-
self.client.table("agent_trigger_links")
|
|
427
|
-
.select("""
|
|
428
|
-
registration_id,
|
|
429
|
-
trigger_registrations(
|
|
430
|
-
*,
|
|
431
|
-
trigger_templates(id, name, description)
|
|
432
|
-
)
|
|
433
|
-
""")
|
|
434
|
-
.eq("agent_id", agent_id)
|
|
435
|
-
.execute()
|
|
436
|
-
)
|
|
437
|
-
|
|
438
|
-
return [row["trigger_registrations"] for row in response.data or []]
|
|
439
|
-
|
|
440
|
-
except Exception as e:
|
|
441
|
-
logger.error(f"Error getting triggers for agent: {e}")
|
|
442
|
-
return []
|
|
443
|
-
|
|
444
|
-
# ========== Helper Methods ==========
|
|
445
|
-
|
|
446
|
-
async def get_user_from_token(self, token: str) -> str | None:
|
|
447
|
-
"""Extract user ID from JWT token via Supabase auth."""
|
|
448
|
-
try:
|
|
449
|
-
client = self._create_user_client(token)
|
|
450
|
-
response = client.auth.get_user(token)
|
|
451
|
-
return response.user.id if response.user else None
|
|
452
|
-
except Exception as e:
|
|
453
|
-
logger.error(f"Error getting user from token: {e}")
|
|
454
|
-
return None
|
|
455
|
-
|
|
456
|
-
async def get_user_by_email(self, email: str) -> str | None:
|
|
457
|
-
"""Get user ID by email from trigger registrations."""
|
|
458
|
-
try:
|
|
459
|
-
response = (
|
|
460
|
-
self.client.table("trigger_registrations")
|
|
461
|
-
.select("user_id")
|
|
462
|
-
.eq("resource->>email", email)
|
|
463
|
-
.limit(1)
|
|
464
|
-
.execute()
|
|
465
|
-
)
|
|
466
|
-
|
|
467
|
-
return response.data[0]["user_id"] if response.data else None
|
|
468
|
-
|
|
469
|
-
except Exception as e:
|
|
470
|
-
logger.error(f"Error getting user by email: {e}")
|
|
471
|
-
return None
|
|
File without changes
|