langchain-trigger-server 0.2.3__tar.gz → 0.2.5__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.3 → langchain_trigger_server-0.2.5}/PKG-INFO +1 -1
- {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/app.py +51 -9
- {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/cron_manager.py +18 -34
- {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/database/__init__.py +1 -1
- {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/triggers/cron_trigger.py +4 -1
- {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/pyproject.toml +1 -1
- {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/.github/workflows/release.yml +0 -0
- {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/.vscode/settings.json +0 -0
- {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/README.md +0 -0
- {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/__init__.py +0 -0
- {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/core.py +0 -0
- {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/database/interface.py +0 -0
- {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/database/supabase.py +0 -0
- {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/decorators.py +0 -0
- {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/triggers/__init__.py +0 -0
- {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/test_framework.py +0 -0
- {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/uv.lock +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.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
|
|
@@ -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
|
|
@@ -406,21 +417,21 @@ class TriggerServer:
|
|
|
406
417
|
"""Remove an agent from a trigger registration."""
|
|
407
418
|
try:
|
|
408
419
|
user_id = current_user["identity"]
|
|
409
|
-
|
|
420
|
+
|
|
410
421
|
# Verify the trigger registration exists and belongs to the user
|
|
411
422
|
registration = await self.database.get_trigger_registration(registration_id, user_id)
|
|
412
423
|
if not registration:
|
|
413
424
|
raise HTTPException(status_code=404, detail="Trigger registration not found or access denied")
|
|
414
|
-
|
|
425
|
+
|
|
415
426
|
# Unlink the agent from the trigger
|
|
416
427
|
success = await self.database.unlink_agent_from_trigger(
|
|
417
428
|
agent_id=agent_id,
|
|
418
429
|
registration_id=registration_id
|
|
419
430
|
)
|
|
420
|
-
|
|
431
|
+
|
|
421
432
|
if not success:
|
|
422
433
|
raise HTTPException(status_code=500, detail="Failed to unlink agent from trigger")
|
|
423
|
-
|
|
434
|
+
|
|
424
435
|
return {
|
|
425
436
|
"success": True,
|
|
426
437
|
"message": f"Successfully unlinked agent {agent_id} from trigger {registration_id}",
|
|
@@ -429,12 +440,43 @@ class TriggerServer:
|
|
|
429
440
|
"agent_id": agent_id
|
|
430
441
|
}
|
|
431
442
|
}
|
|
432
|
-
|
|
443
|
+
|
|
433
444
|
except HTTPException:
|
|
434
445
|
raise
|
|
435
446
|
except Exception as e:
|
|
436
447
|
logger.error(f"Error unlinking agent from trigger: {e}")
|
|
437
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))
|
|
438
480
|
|
|
439
481
|
|
|
440
482
|
async def _handle_request(
|
|
@@ -552,4 +594,4 @@ class TriggerServer:
|
|
|
552
594
|
|
|
553
595
|
def get_app(self) -> FastAPI:
|
|
554
596
|
"""Get the FastAPI app instance."""
|
|
555
|
-
return self.app
|
|
597
|
+
return self.app
|
{langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/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
|
|
|
@@ -42,7 +43,6 @@ class CronTriggerManager:
|
|
|
42
43
|
try:
|
|
43
44
|
self.scheduler.start()
|
|
44
45
|
await self._load_existing_registrations()
|
|
45
|
-
logger.info("✓ CronTriggerManager started")
|
|
46
46
|
except Exception as e:
|
|
47
47
|
logger.error(f"Failed to start CronTriggerManager: {e}")
|
|
48
48
|
raise
|
|
@@ -51,14 +51,13 @@ class CronTriggerManager:
|
|
|
51
51
|
"""Shutdown scheduler gracefully."""
|
|
52
52
|
try:
|
|
53
53
|
self.scheduler.shutdown(wait=True)
|
|
54
|
-
logger.info("✓ CronTriggerManager stopped")
|
|
55
54
|
except Exception as e:
|
|
56
55
|
logger.error(f"Error shutting down CronTriggerManager: {e}")
|
|
57
56
|
|
|
58
57
|
async def _load_existing_registrations(self):
|
|
59
58
|
"""Load all existing cron registrations from database and schedule them."""
|
|
60
59
|
try:
|
|
61
|
-
registrations = await self.trigger_server.database.get_all_registrations(
|
|
60
|
+
registrations = await self.trigger_server.database.get_all_registrations(CRON_TRIGGER_ID)
|
|
62
61
|
|
|
63
62
|
scheduled_count = 0
|
|
64
63
|
for registration in registrations:
|
|
@@ -68,8 +67,6 @@ class CronTriggerManager:
|
|
|
68
67
|
scheduled_count += 1
|
|
69
68
|
except Exception as e:
|
|
70
69
|
logger.error(f"Failed to schedule existing cron job {registration.get('id')}: {e}")
|
|
71
|
-
|
|
72
|
-
logger.info(f"Loaded {scheduled_count} existing cron registrations from {len(registrations)} total")
|
|
73
70
|
|
|
74
71
|
except Exception as e:
|
|
75
72
|
logger.error(f"Failed to load existing cron registrations: {e}")
|
|
@@ -83,18 +80,16 @@ class CronTriggerManager:
|
|
|
83
80
|
|
|
84
81
|
# Reload from database
|
|
85
82
|
await self._load_existing_registrations()
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
|
|
88
84
|
except Exception as e:
|
|
89
85
|
logger.error(f"Failed to reload cron jobs from database: {e}")
|
|
90
86
|
raise
|
|
91
87
|
|
|
92
88
|
async def on_registration_created(self, registration: Dict[str, Any]):
|
|
93
89
|
"""Called when a new cron registration is created."""
|
|
94
|
-
if registration.get("trigger_template_id") ==
|
|
90
|
+
if registration.get("trigger_template_id") == CRON_TRIGGER_ID:
|
|
95
91
|
try:
|
|
96
92
|
await self._schedule_cron_job(registration)
|
|
97
|
-
logger.info(f"Scheduled new cron job for registration {registration['id']}")
|
|
98
93
|
except Exception as e:
|
|
99
94
|
logger.error(f"Failed to schedule new cron job {registration['id']}: {e}")
|
|
100
95
|
raise
|
|
@@ -103,7 +98,6 @@ class CronTriggerManager:
|
|
|
103
98
|
"""Called when a cron registration is deleted."""
|
|
104
99
|
try:
|
|
105
100
|
await self._unschedule_cron_job(registration_id)
|
|
106
|
-
logger.info(f"Unscheduled cron job for deleted registration {registration_id}")
|
|
107
101
|
except Exception as e:
|
|
108
102
|
logger.error(f"Failed to unschedule cron job {registration_id}: {e}")
|
|
109
103
|
|
|
@@ -144,10 +138,9 @@ class CronTriggerManager:
|
|
|
144
138
|
max_instances=1, # Prevent overlapping executions
|
|
145
139
|
replace_existing=True
|
|
146
140
|
)
|
|
147
|
-
|
|
141
|
+
|
|
148
142
|
self.active_jobs[registration_id] = job.id
|
|
149
|
-
|
|
150
|
-
|
|
143
|
+
|
|
151
144
|
except Exception as e:
|
|
152
145
|
logger.error(f"Failed to schedule cron job for registration {registration_id}: {e}")
|
|
153
146
|
raise
|
|
@@ -159,7 +152,6 @@ class CronTriggerManager:
|
|
|
159
152
|
try:
|
|
160
153
|
self.scheduler.remove_job(job_id)
|
|
161
154
|
del self.active_jobs[registration_id]
|
|
162
|
-
logger.info(f"✓ Unscheduled cron job for registration {registration_id}")
|
|
163
155
|
except Exception as e:
|
|
164
156
|
logger.error(f"Failed to unschedule cron job {job_id}: {e}")
|
|
165
157
|
raise
|
|
@@ -171,9 +163,6 @@ class CronTriggerManager:
|
|
|
171
163
|
registration_id = registration["id"]
|
|
172
164
|
cron_pattern = registration["resource"]["crontab"]
|
|
173
165
|
|
|
174
|
-
# Log immediately when callback is invoked to verify APScheduler is calling us
|
|
175
|
-
logger.info(f"Cron callback invoked for job {registration_id}")
|
|
176
|
-
|
|
177
166
|
execution = CronJobExecution(
|
|
178
167
|
registration_id=registration_id,
|
|
179
168
|
cron_pattern=cron_pattern,
|
|
@@ -182,16 +171,14 @@ class CronTriggerManager:
|
|
|
182
171
|
status="running"
|
|
183
172
|
)
|
|
184
173
|
|
|
185
|
-
logger.info(f"Executing cron job {registration_id} with pattern '{cron_pattern}'")
|
|
186
|
-
|
|
187
174
|
try:
|
|
188
|
-
agents_invoked = await self.
|
|
175
|
+
agents_invoked = await self.execute_cron_job(registration)
|
|
189
176
|
execution.status = "completed"
|
|
190
177
|
execution.agents_invoked = agents_invoked
|
|
191
|
-
logger.info(f"✓ Cron job {registration_id} completed - invoked {agents_invoked}
|
|
192
|
-
|
|
178
|
+
logger.info(f"✓ Cron job {registration_id} completed successfully - invoked {agents_invoked} agent(s)")
|
|
179
|
+
|
|
193
180
|
except Exception as e:
|
|
194
|
-
execution.status = "failed"
|
|
181
|
+
execution.status = "failed"
|
|
195
182
|
execution.error_message = str(e)
|
|
196
183
|
logger.error(f"✗ Cron job {registration_id} failed: {e}")
|
|
197
184
|
|
|
@@ -199,15 +186,13 @@ class CronTriggerManager:
|
|
|
199
186
|
execution.completion_time = datetime.utcnow()
|
|
200
187
|
await self._record_execution(execution)
|
|
201
188
|
|
|
202
|
-
async def
|
|
203
|
-
"""Execute a
|
|
189
|
+
async def execute_cron_job(self, registration: Dict[str, Any]) -> int:
|
|
190
|
+
"""Execute a cron job - invoke agents. Can be called manually or by scheduler."""
|
|
204
191
|
registration_id = registration["id"]
|
|
205
192
|
user_id = registration["user_id"]
|
|
206
|
-
|
|
193
|
+
|
|
207
194
|
# Get agent links
|
|
208
|
-
logger.info(f"Querying database for agents linked to cron job {registration_id}")
|
|
209
195
|
agent_links = await self.trigger_server.database.get_agents_for_trigger(registration_id)
|
|
210
|
-
logger.info(f"Found {len(agent_links) if agent_links else 0} agent links for cron job {registration_id}")
|
|
211
196
|
|
|
212
197
|
if not agent_links:
|
|
213
198
|
logger.warning(f"No agents linked to cron job {registration_id}")
|
|
@@ -216,16 +201,16 @@ class CronTriggerManager:
|
|
|
216
201
|
agents_invoked = 0
|
|
217
202
|
for agent_link in agent_links:
|
|
218
203
|
agent_id = agent_link if isinstance(agent_link, str) else agent_link.get("agent_id")
|
|
219
|
-
|
|
204
|
+
|
|
220
205
|
current_time = datetime.utcnow()
|
|
221
206
|
current_time_str = current_time.strftime("%A, %B %d, %Y at %H:%M UTC")
|
|
222
|
-
|
|
207
|
+
|
|
223
208
|
agent_input = {
|
|
224
209
|
"messages": [
|
|
225
210
|
{"role": "human", "content": f"ACTION: triggering cron from langchain-trigger-server\nCURRENT TIME: {current_time_str}"}
|
|
226
211
|
]
|
|
227
212
|
}
|
|
228
|
-
|
|
213
|
+
|
|
229
214
|
try:
|
|
230
215
|
success = await self.trigger_server._invoke_agent(
|
|
231
216
|
agent_id=agent_id,
|
|
@@ -234,11 +219,10 @@ class CronTriggerManager:
|
|
|
234
219
|
)
|
|
235
220
|
if success:
|
|
236
221
|
agents_invoked += 1
|
|
237
|
-
|
|
238
|
-
|
|
222
|
+
|
|
239
223
|
except Exception as e:
|
|
240
224
|
logger.error(f"✗ Error invoking agent {agent_id} for cron job {registration_id}: {e}")
|
|
241
|
-
|
|
225
|
+
|
|
242
226
|
return agents_invoked
|
|
243
227
|
|
|
244
228
|
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":
|
|
@@ -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",
|
{langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/.github/workflows/release.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/__init__.py
RENAMED
|
File without changes
|
{langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/core.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/decorators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|