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.

Files changed (17) hide show
  1. {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/PKG-INFO +1 -1
  2. {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/app.py +51 -9
  3. {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/cron_manager.py +18 -34
  4. {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/database/__init__.py +1 -1
  5. {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/triggers/cron_trigger.py +4 -1
  6. {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/pyproject.toml +1 -1
  7. {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/.github/workflows/release.yml +0 -0
  8. {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/.vscode/settings.json +0 -0
  9. {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/README.md +0 -0
  10. {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/__init__.py +0 -0
  11. {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/core.py +0 -0
  12. {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/database/interface.py +0 -0
  13. {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/database/supabase.py +0 -0
  14. {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/decorators.py +0 -0
  15. {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/langchain_triggers/triggers/__init__.py +0 -0
  16. {langchain_trigger_server-0.2.3 → langchain_trigger_server-0.2.5}/test_framework.py +0 -0
  17. {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
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
- self.database = create_database()
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
@@ -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("cron-trigger")
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
- logger.info("✓ Reloaded cron jobs from database")
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") == "cron-trigger":
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
- logger.info(f"✓ Scheduled cron job: '{crontab}' for registration {registration_id}")
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._execute_cron_job(registration)
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} agents")
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 _execute_cron_job(self, registration: Dict[str, Any]) -> int:
203
- """Execute a scheduled cron job - invoke agents."""
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
- logger.info(f"✓ Invoked agent {agent_id} for cron job {registration_id}")
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 = "supabase", **kwargs) -> TriggerDatabaseInterface:
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="cron-trigger",
94
+ id=CRON_TRIGGER_ID,
92
95
  name="Cron Scheduler",
93
96
  provider="Cron",
94
97
  description="Triggers agents on a cron schedule",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "langchain-trigger-server"
7
- version = "0.2.3"
7
+ version = "0.2.5"
8
8
  description = "Generic event-driven triggers framework"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"