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.

Files changed (17) hide show
  1. {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/PKG-INFO +1 -1
  2. {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/app.py +54 -15
  3. {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/cron_manager.py +24 -18
  4. {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/database/__init__.py +1 -1
  5. {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/database/supabase.py +2 -2
  6. {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/triggers/cron_trigger.py +4 -1
  7. {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/pyproject.toml +1 -1
  8. {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/uv.lock +828 -826
  9. {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/.github/workflows/release.yml +0 -0
  10. {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/.vscode/settings.json +0 -0
  11. {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/README.md +0 -0
  12. {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/__init__.py +0 -0
  13. {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/core.py +0 -0
  14. {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/database/interface.py +0 -0
  15. {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/decorators.py +0 -0
  16. {langchain_trigger_server-0.2.2 → langchain_trigger_server-0.2.4}/langchain_triggers/triggers/__init__.py +0 -0
  17. {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.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
- 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
@@ -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
@@ -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("cron-trigger")
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") == "cron-trigger":
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"🕐 Executing cron job {registration_id} with pattern '{cron_pattern}'")
183
-
185
+
186
+ logger.info(f"Executing cron job {registration_id} with pattern '{cron_pattern}'")
187
+
184
188
  try:
185
- agents_invoked = await self._execute_cron_job(registration)
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 _execute_cron_job(self, registration: Dict[str, Any]) -> int:
200
- """Execute a scheduled cron job - invoke agents."""
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 = "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":
@@ -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("trigger_templates.id", template_id)
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("trigger_templates.id", template_id).eq("user_id", user_id)
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="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.2"
7
+ version = "0.2.4"
8
8
  description = "Generic event-driven triggers framework"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"