langchain-trigger-server 0.2.2__tar.gz → 0.2.4__tar.gz
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.2.2 → langchain_trigger_server-0.2.4}/PKG-INFO +1 -1
- {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/app.py +54 -15
- {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/cron_manager.py +24 -18
- {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/database/__init__.py +1 -1
- {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/database/supabase.py +2 -2
- {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/triggers/cron_trigger.py +4 -1
- {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/pyproject.toml +1 -1
- {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/uv.lock +828 -826
- {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/.github/workflows/release.yml +0 -0
- {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/.vscode/settings.json +0 -0
- {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/README.md +0 -0
- {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/__init__.py +0 -0
- {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/core.py +0 -0
- {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/database/interface.py +0 -0
- {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/decorators.py +0 -0
- {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/triggers/__init__.py +0 -0
- {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/test_framework.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langchain-trigger-server
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
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
|
|
@@ -15,6 +15,7 @@ from starlette.responses import Response
|
|
|
15
15
|
from .decorators import TriggerTemplate
|
|
16
16
|
from .database import create_database, TriggerDatabaseInterface
|
|
17
17
|
from .cron_manager import CronTriggerManager
|
|
18
|
+
from .triggers.cron_trigger import CRON_TRIGGER_ID
|
|
18
19
|
|
|
19
20
|
logger = logging.getLogger(__name__)
|
|
20
21
|
|
|
@@ -71,17 +72,27 @@ class TriggerServer:
|
|
|
71
72
|
def __init__(
|
|
72
73
|
self,
|
|
73
74
|
auth_handler: Callable,
|
|
75
|
+
database: Optional[TriggerDatabaseInterface] = None,
|
|
76
|
+
database_type: Optional[str] = "supabase",
|
|
77
|
+
**database_kwargs: Any,
|
|
74
78
|
):
|
|
75
79
|
# Configure uvicorn logging to use consistent formatting
|
|
76
80
|
self._configure_uvicorn_logging()
|
|
77
|
-
|
|
81
|
+
|
|
78
82
|
self.app = FastAPI(
|
|
79
83
|
title="Triggers Server",
|
|
80
84
|
description="Event-driven triggers framework",
|
|
81
85
|
version="0.1.0"
|
|
82
86
|
)
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
|
|
88
|
+
# Configure database: allow either instance injection or factory creation
|
|
89
|
+
# Defaults to Supabase for backward compatibility
|
|
90
|
+
if database and database_type != "supabase":
|
|
91
|
+
raise ValueError("Provide either 'database' or 'database_type', not both")
|
|
92
|
+
if database is not None:
|
|
93
|
+
self.database = database
|
|
94
|
+
else:
|
|
95
|
+
self.database = create_database(database_type, **database_kwargs)
|
|
85
96
|
self.auth_handler = auth_handler
|
|
86
97
|
|
|
87
98
|
# LangGraph configuration
|
|
@@ -261,7 +272,7 @@ class TriggerServer:
|
|
|
261
272
|
try:
|
|
262
273
|
payload = await request.json()
|
|
263
274
|
logger.info(f"Registration payload received: {payload}")
|
|
264
|
-
|
|
275
|
+
|
|
265
276
|
user_id = current_user["identity"]
|
|
266
277
|
trigger_id = payload.get("type")
|
|
267
278
|
if not trigger_id:
|
|
@@ -270,7 +281,7 @@ class TriggerServer:
|
|
|
270
281
|
trigger = next((t for t in self.triggers if t.id == trigger_id), None)
|
|
271
282
|
if not trigger:
|
|
272
283
|
raise HTTPException(status_code=400, detail=f"Unknown trigger type: {trigger_id}")
|
|
273
|
-
|
|
284
|
+
|
|
274
285
|
# Parse payload into registration model first
|
|
275
286
|
try:
|
|
276
287
|
registration_instance = trigger.registration_model(**payload)
|
|
@@ -287,15 +298,12 @@ class TriggerServer:
|
|
|
287
298
|
template_id=trigger.id,
|
|
288
299
|
resource_data=resource_dict
|
|
289
300
|
)
|
|
290
|
-
|
|
301
|
+
|
|
291
302
|
if existing_registration:
|
|
292
303
|
raise HTTPException(
|
|
293
304
|
status_code=400,
|
|
294
305
|
detail=f"You already have a registration with this configuration for trigger type '{trigger.id}'. Registration ID: {existing_registration.get('id')}"
|
|
295
306
|
)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
# Call the trigger's registration handler with parsed registration model
|
|
299
307
|
result = await trigger.registration_handler(user_id, self.langchain_auth_client, registration_instance)
|
|
300
308
|
|
|
301
309
|
# Check if handler requested to skip registration (e.g., for OAuth or URL verification)
|
|
@@ -409,21 +417,21 @@ class TriggerServer:
|
|
|
409
417
|
"""Remove an agent from a trigger registration."""
|
|
410
418
|
try:
|
|
411
419
|
user_id = current_user["identity"]
|
|
412
|
-
|
|
420
|
+
|
|
413
421
|
# Verify the trigger registration exists and belongs to the user
|
|
414
422
|
registration = await self.database.get_trigger_registration(registration_id, user_id)
|
|
415
423
|
if not registration:
|
|
416
424
|
raise HTTPException(status_code=404, detail="Trigger registration not found or access denied")
|
|
417
|
-
|
|
425
|
+
|
|
418
426
|
# Unlink the agent from the trigger
|
|
419
427
|
success = await self.database.unlink_agent_from_trigger(
|
|
420
428
|
agent_id=agent_id,
|
|
421
429
|
registration_id=registration_id
|
|
422
430
|
)
|
|
423
|
-
|
|
431
|
+
|
|
424
432
|
if not success:
|
|
425
433
|
raise HTTPException(status_code=500, detail="Failed to unlink agent from trigger")
|
|
426
|
-
|
|
434
|
+
|
|
427
435
|
return {
|
|
428
436
|
"success": True,
|
|
429
437
|
"message": f"Successfully unlinked agent {agent_id} from trigger {registration_id}",
|
|
@@ -432,12 +440,43 @@ class TriggerServer:
|
|
|
432
440
|
"agent_id": agent_id
|
|
433
441
|
}
|
|
434
442
|
}
|
|
435
|
-
|
|
443
|
+
|
|
436
444
|
except HTTPException:
|
|
437
445
|
raise
|
|
438
446
|
except Exception as e:
|
|
439
447
|
logger.error(f"Error unlinking agent from trigger: {e}")
|
|
440
448
|
raise HTTPException(status_code=500, detail=str(e))
|
|
449
|
+
|
|
450
|
+
@self.app.post("/api/triggers/registrations/{registration_id}/execute")
|
|
451
|
+
async def api_execute_trigger_now(registration_id: str, current_user: Dict[str, Any] = Depends(get_current_user)) -> Dict[str, Any]:
|
|
452
|
+
"""Manually execute a cron trigger registration immediately."""
|
|
453
|
+
try:
|
|
454
|
+
user_id = current_user["identity"]
|
|
455
|
+
|
|
456
|
+
# Verify the trigger registration exists and belongs to the user
|
|
457
|
+
registration = await self.database.get_trigger_registration(registration_id, user_id)
|
|
458
|
+
if not registration:
|
|
459
|
+
raise HTTPException(status_code=404, detail="Trigger registration not found or access denied")
|
|
460
|
+
|
|
461
|
+
# Get the template to check if it's a cron trigger
|
|
462
|
+
template_id = registration.get("template_id")
|
|
463
|
+
if template_id != CRON_TRIGGER_ID:
|
|
464
|
+
raise HTTPException(status_code=400, detail="Manual execution is only supported for cron triggers")
|
|
465
|
+
|
|
466
|
+
# Execute the cron trigger using the cron manager
|
|
467
|
+
agents_invoked = await self.cron_manager.execute_cron_job(registration)
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
"success": True,
|
|
471
|
+
"message": f"Manually executed cron trigger {registration_id}",
|
|
472
|
+
"agents_invoked": agents_invoked
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
except HTTPException:
|
|
476
|
+
raise
|
|
477
|
+
except Exception as e:
|
|
478
|
+
logger.error(f"Error executing trigger: {e}")
|
|
479
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
441
480
|
|
|
442
481
|
|
|
443
482
|
async def _handle_request(
|
|
@@ -555,4 +594,4 @@ class TriggerServer:
|
|
|
555
594
|
|
|
556
595
|
def get_app(self) -> FastAPI:
|
|
557
596
|
"""Get the FastAPI app instance."""
|
|
558
|
-
return self.app
|
|
597
|
+
return self.app
|
{langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/cron_manager.py
RENAMED
|
@@ -10,6 +10,7 @@ from croniter import croniter
|
|
|
10
10
|
from pydantic import BaseModel
|
|
11
11
|
|
|
12
12
|
from langchain_triggers.core import TriggerHandlerResult
|
|
13
|
+
from langchain_triggers.triggers.cron_trigger import CRON_TRIGGER_ID
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
15
16
|
|
|
@@ -58,7 +59,7 @@ class CronTriggerManager:
|
|
|
58
59
|
async def _load_existing_registrations(self):
|
|
59
60
|
"""Load all existing cron registrations from database and schedule them."""
|
|
60
61
|
try:
|
|
61
|
-
registrations = await self.trigger_server.database.get_all_registrations(
|
|
62
|
+
registrations = await self.trigger_server.database.get_all_registrations(CRON_TRIGGER_ID)
|
|
62
63
|
|
|
63
64
|
scheduled_count = 0
|
|
64
65
|
for registration in registrations:
|
|
@@ -91,7 +92,7 @@ class CronTriggerManager:
|
|
|
91
92
|
|
|
92
93
|
async def on_registration_created(self, registration: Dict[str, Any]):
|
|
93
94
|
"""Called when a new cron registration is created."""
|
|
94
|
-
if registration.get("trigger_template_id") ==
|
|
95
|
+
if registration.get("trigger_template_id") == CRON_TRIGGER_ID:
|
|
95
96
|
try:
|
|
96
97
|
await self._schedule_cron_job(registration)
|
|
97
98
|
logger.info(f"Scheduled new cron job for registration {registration['id']}")
|
|
@@ -170,7 +171,10 @@ class CronTriggerManager:
|
|
|
170
171
|
"""Execute a scheduled cron job with full monitoring and error handling."""
|
|
171
172
|
registration_id = registration["id"]
|
|
172
173
|
cron_pattern = registration["resource"]["crontab"]
|
|
173
|
-
|
|
174
|
+
|
|
175
|
+
# Log immediately when callback is invoked to verify APScheduler is calling us
|
|
176
|
+
logger.info(f"Cron callback invoked for job {registration_id}")
|
|
177
|
+
|
|
174
178
|
execution = CronJobExecution(
|
|
175
179
|
registration_id=registration_id,
|
|
176
180
|
cron_pattern=cron_pattern,
|
|
@@ -178,17 +182,17 @@ class CronTriggerManager:
|
|
|
178
182
|
actual_start_time=datetime.utcnow(),
|
|
179
183
|
status="running"
|
|
180
184
|
)
|
|
181
|
-
|
|
182
|
-
logger.info(f"
|
|
183
|
-
|
|
185
|
+
|
|
186
|
+
logger.info(f"Executing cron job {registration_id} with pattern '{cron_pattern}'")
|
|
187
|
+
|
|
184
188
|
try:
|
|
185
|
-
agents_invoked = await self.
|
|
189
|
+
agents_invoked = await self.execute_cron_job(registration)
|
|
186
190
|
execution.status = "completed"
|
|
187
191
|
execution.agents_invoked = agents_invoked
|
|
188
192
|
logger.info(f"✓ Cron job {registration_id} completed - invoked {agents_invoked} agents")
|
|
189
|
-
|
|
193
|
+
|
|
190
194
|
except Exception as e:
|
|
191
|
-
execution.status = "failed"
|
|
195
|
+
execution.status = "failed"
|
|
192
196
|
execution.error_message = str(e)
|
|
193
197
|
logger.error(f"✗ Cron job {registration_id} failed: {e}")
|
|
194
198
|
|
|
@@ -196,14 +200,16 @@ class CronTriggerManager:
|
|
|
196
200
|
execution.completion_time = datetime.utcnow()
|
|
197
201
|
await self._record_execution(execution)
|
|
198
202
|
|
|
199
|
-
async def
|
|
200
|
-
"""Execute a
|
|
203
|
+
async def execute_cron_job(self, registration: Dict[str, Any]) -> int:
|
|
204
|
+
"""Execute a cron job - invoke agents. Can be called manually or by scheduler."""
|
|
201
205
|
registration_id = registration["id"]
|
|
202
206
|
user_id = registration["user_id"]
|
|
203
|
-
|
|
207
|
+
|
|
204
208
|
# Get agent links
|
|
209
|
+
logger.info(f"Querying database for agents linked to cron job {registration_id}")
|
|
205
210
|
agent_links = await self.trigger_server.database.get_agents_for_trigger(registration_id)
|
|
206
|
-
|
|
211
|
+
logger.info(f"Found {len(agent_links) if agent_links else 0} agent links for cron job {registration_id}")
|
|
212
|
+
|
|
207
213
|
if not agent_links:
|
|
208
214
|
logger.warning(f"No agents linked to cron job {registration_id}")
|
|
209
215
|
return 0
|
|
@@ -211,16 +217,16 @@ class CronTriggerManager:
|
|
|
211
217
|
agents_invoked = 0
|
|
212
218
|
for agent_link in agent_links:
|
|
213
219
|
agent_id = agent_link if isinstance(agent_link, str) else agent_link.get("agent_id")
|
|
214
|
-
|
|
220
|
+
|
|
215
221
|
current_time = datetime.utcnow()
|
|
216
222
|
current_time_str = current_time.strftime("%A, %B %d, %Y at %H:%M UTC")
|
|
217
|
-
|
|
223
|
+
|
|
218
224
|
agent_input = {
|
|
219
225
|
"messages": [
|
|
220
226
|
{"role": "human", "content": f"ACTION: triggering cron from langchain-trigger-server\nCURRENT TIME: {current_time_str}"}
|
|
221
227
|
]
|
|
222
228
|
}
|
|
223
|
-
|
|
229
|
+
|
|
224
230
|
try:
|
|
225
231
|
success = await self.trigger_server._invoke_agent(
|
|
226
232
|
agent_id=agent_id,
|
|
@@ -230,10 +236,10 @@ class CronTriggerManager:
|
|
|
230
236
|
if success:
|
|
231
237
|
agents_invoked += 1
|
|
232
238
|
logger.info(f"✓ Invoked agent {agent_id} for cron job {registration_id}")
|
|
233
|
-
|
|
239
|
+
|
|
234
240
|
except Exception as e:
|
|
235
241
|
logger.error(f"✗ Error invoking agent {agent_id} for cron job {registration_id}: {e}")
|
|
236
|
-
|
|
242
|
+
|
|
237
243
|
return agents_invoked
|
|
238
244
|
|
|
239
245
|
async def _record_execution(self, execution: CronJobExecution):
|
|
@@ -4,7 +4,7 @@ from .interface import TriggerDatabaseInterface
|
|
|
4
4
|
from .supabase import SupabaseTriggerDatabase
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def create_database(database_type: str
|
|
7
|
+
def create_database(database_type: str, **kwargs) -> TriggerDatabaseInterface:
|
|
8
8
|
"""Factory function to create database implementation."""
|
|
9
9
|
|
|
10
10
|
if database_type == "supabase":
|
|
@@ -259,7 +259,7 @@ class SupabaseTriggerDatabase(TriggerDatabaseInterface):
|
|
|
259
259
|
# Build query to match against trigger_registrations with template_id filter
|
|
260
260
|
query = self.client.table("trigger_registrations").select(
|
|
261
261
|
"*, trigger_templates(id, name, description)"
|
|
262
|
-
).eq("
|
|
262
|
+
).eq("template_id", template_id)
|
|
263
263
|
|
|
264
264
|
# Add resource field matches
|
|
265
265
|
for field, value in resource_data.items():
|
|
@@ -286,7 +286,7 @@ class SupabaseTriggerDatabase(TriggerDatabaseInterface):
|
|
|
286
286
|
# Build query to match against trigger_registrations with template_id and user_id filter
|
|
287
287
|
query = self.client.table("trigger_registrations").select(
|
|
288
288
|
"*, trigger_templates(id, name, description)"
|
|
289
|
-
).eq("
|
|
289
|
+
).eq("template_id", template_id).eq("user_id", user_id)
|
|
290
290
|
|
|
291
291
|
# Add resource field matches
|
|
292
292
|
for field, value in resource_data.items():
|
|
@@ -17,6 +17,9 @@ from langchain_triggers.decorators import TriggerTemplate
|
|
|
17
17
|
|
|
18
18
|
logger = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
|
+
# Global constant for cron trigger ID
|
|
21
|
+
CRON_TRIGGER_ID = "cron-trigger"
|
|
22
|
+
|
|
20
23
|
|
|
21
24
|
class CronRegistration(TriggerRegistrationModel):
|
|
22
25
|
"""Registration model for cron triggers - just a crontab pattern."""
|
|
@@ -88,7 +91,7 @@ async def cron_trigger_handler(
|
|
|
88
91
|
|
|
89
92
|
|
|
90
93
|
cron_trigger = TriggerTemplate(
|
|
91
|
-
id=
|
|
94
|
+
id=CRON_TRIGGER_ID,
|
|
92
95
|
name="Cron Scheduler",
|
|
93
96
|
provider="Cron",
|
|
94
97
|
description="Triggers agents on a cron schedule",
|