langchain-trigger-server 0.1.7__tar.gz → 0.1.9__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langchain-trigger-server
3
- Version: 0.1.7
3
+ Version: 0.1.9
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
@@ -1,6 +1,6 @@
1
1
  """LangChain Triggers Framework - Event-driven triggers for AI agents."""
2
2
 
3
- from .core import UserAuthInfo, TriggerRegistrationModel, TriggerHandlerResult, TriggerRegistrationResult, MetadataManager
3
+ from .core import UserAuthInfo, TriggerRegistrationModel, TriggerHandlerResult, TriggerRegistrationResult
4
4
  from .decorators import TriggerTemplate
5
5
  from .app import TriggerServer
6
6
 
@@ -11,7 +11,6 @@ __all__ = [
11
11
  "TriggerRegistrationModel",
12
12
  "TriggerHandlerResult",
13
13
  "TriggerRegistrationResult",
14
- "MetadataManager",
15
14
  "TriggerTemplate",
16
15
  "TriggerServer",
17
16
  ]
@@ -6,14 +6,12 @@ import logging
6
6
  import os
7
7
  from typing import Any, Callable, Dict, List, Optional
8
8
 
9
- import httpx
10
-
11
9
  from fastapi import FastAPI, HTTPException, Request, Depends
12
10
  from langgraph_sdk import get_client
11
+ from langchain_auth.client import Client
13
12
  from starlette.middleware.base import BaseHTTPMiddleware
14
13
  from starlette.responses import Response
15
14
 
16
- from .core import UserAuthInfo, ProviderAuthInfo, MetadataManager
17
15
  from .decorators import TriggerTemplate
18
16
  from .database import create_database, TriggerDatabaseInterface
19
17
 
@@ -72,7 +70,6 @@ class TriggerServer:
72
70
  def __init__(
73
71
  self,
74
72
  auth_handler: Callable,
75
- langgraph_headers_builder: Callable,
76
73
  ):
77
74
  self.app = FastAPI(
78
75
  title="Triggers Server",
@@ -82,11 +79,10 @@ class TriggerServer:
82
79
 
83
80
  self.database = create_database()
84
81
  self.auth_handler = auth_handler
85
- self.langgraph_headers_builder = langgraph_headers_builder
86
82
 
87
83
  # LangGraph configuration
88
84
  self.langgraph_api_url = os.getenv("LANGGRAPH_API_URL")
89
- self.langgraph_api_key = os.getenv("LANGCHAIN_API_KEY")
85
+ self.langsmith_api_key = os.getenv("LANGSMITH_API_KEY")
90
86
 
91
87
  if not self.langgraph_api_url:
92
88
  raise ValueError("LANGGRAPH_API_URL environment variable is required")
@@ -94,19 +90,16 @@ class TriggerServer:
94
90
  self.langgraph_api_url = self.langgraph_api_url.rstrip("/")
95
91
 
96
92
  # Initialize LangGraph SDK client
97
- self.langgraph_client = get_client(url=self.langgraph_api_url)
93
+ self.langgraph_client = get_client(url=self.langgraph_api_url, api_key=self.langsmith_api_key)
98
94
 
99
- self.langchain_auth_client = None
100
- try:
101
- from langchain_auth import Client
102
- langchain_api_key = os.getenv("LANGCHAIN_API_KEY")
103
- if langchain_api_key:
104
- self.langchain_auth_client = Client(api_key=langchain_api_key)
105
- logger.info("Initialized LangChain Auth client for OAuth token injection")
106
- else:
107
- logger.warning("LANGCHAIN_API_KEY not found - OAuth token injection disabled")
108
- except ImportError:
109
- logger.warning("langchain_auth not installed - OAuth token injection disabled")
95
+ # Initialize LangChain auth client
96
+ langchain_api_key = os.getenv("LANGCHAIN_API_KEY")
97
+ if langchain_api_key:
98
+ self.langchain_auth_client = Client(api_key=langchain_api_key)
99
+ logger.info("✓ LangChain auth client initialized")
100
+ else:
101
+ self.langchain_auth_client = None
102
+ logger.warning("LANGCHAIN_API_KEY not found - OAuth token injection disabled")
110
103
 
111
104
  self.triggers: List[TriggerTemplate] = []
112
105
 
@@ -255,43 +248,31 @@ class TriggerServer:
255
248
  resource_dict = registration_instance.model_dump()
256
249
  existing_registration = await self.database.find_registration_by_resource(
257
250
  template_id=trigger.id,
258
- resource_data=resource_dict,
259
- user_id=user_id
251
+ resource_data=resource_dict
260
252
  )
261
253
 
254
+ # TODO(sam) figure out how to allow duplicates across users.....very unnatural constraint to have
262
255
  if existing_registration:
263
256
  raise HTTPException(
264
257
  status_code=400,
265
258
  detail=f"A registration with this configuration already exists for trigger type '{trigger.id}'. Registration ID: {existing_registration.get('id')}"
266
259
  )
267
260
 
268
- # Inject OAuth tokens if needed for registration
269
- auth_user = None
270
- if trigger.oauth_providers:
271
- try:
272
- auth_user = await self._get_authenticated_user(trigger, user_id)
273
-
274
- # Check if any provider requires authentication - return early if so
275
- for provider in trigger.oauth_providers.keys():
276
- provider_info = auth_user.providers.get(provider)
277
- if provider_info and provider_info.auth_required:
278
- logger.info(f"User {user_id} needs to authenticate for {provider} - returning auth URL")
279
- return {
280
- "success": True,
281
- "registered": False,
282
- "auth_required": True,
283
- "auth_url": provider_info.auth_url,
284
- "auth_id": provider_info.auth_id,
285
- "provider": provider
286
- }
287
-
288
- except Exception as e:
289
- logger.error(f"OAuth authentication failed during registration: {e}")
290
- raise HTTPException(status_code=500, detail="OAuth authentication failed")
291
-
292
261
 
293
262
  # Call the trigger's registration handler with parsed registration model
294
- result = await trigger.registration_handler(registration_instance, auth_user)
263
+ result = await trigger.registration_handler(user_id, self.langchain_auth_client, registration_instance)
264
+
265
+ # Check if handler requested to skip registration (e.g., for OAuth or URL verification)
266
+ if not result.create_registration:
267
+ logger.info(f"Registration handler requested to skip database creation")
268
+ from fastapi import Response
269
+ import json
270
+ return Response(
271
+ content=json.dumps(result.response_body),
272
+ status_code=result.status_code,
273
+ media_type="application/json"
274
+ )
275
+
295
276
  resource_dict = registration_instance.model_dump()
296
277
 
297
278
  registration = await self.database.create_trigger_registration(
@@ -314,7 +295,7 @@ class TriggerServer:
314
295
  except HTTPException:
315
296
  raise
316
297
  except Exception as e:
317
- logger.error(f"Error creating trigger registration: {e}")
298
+ logger.exception(f"Error creating trigger registration: {e}")
318
299
  raise HTTPException(status_code=500, detail=str(e))
319
300
 
320
301
  @self.app.get("/api/triggers/registrations/{registration_id}/agents")
@@ -418,16 +399,6 @@ class TriggerServer:
418
399
  except Exception as e:
419
400
  logger.error(f"Error unlinking agent from trigger: {e}")
420
401
  raise HTTPException(status_code=500, detail=str(e))
421
-
422
- @self.app.get("/events/subscriptions")
423
- async def list_event_subscriptions() -> Dict[str, Any]:
424
- """List event bus subscriptions."""
425
- if hasattr(self.event_bus, "list_subscriptions"):
426
- subscriptions = self.event_bus.list_subscriptions()
427
- else:
428
- subscriptions = {}
429
-
430
- return {"subscriptions": subscriptions}
431
402
 
432
403
 
433
404
  async def _handle_request(
@@ -437,23 +408,6 @@ class TriggerServer:
437
408
  ) -> Dict[str, Any]:
438
409
  """Handle an incoming request with a handler function."""
439
410
  try:
440
- # Step 1: API Key Authentication (required for webhooks)
441
- api_key = request.headers.get("x-api-key")
442
- if not api_key:
443
- logger.warning("Webhook request missing x-api-key header")
444
- raise HTTPException(
445
- status_code=401,
446
- detail="Missing x-api-key header"
447
- )
448
-
449
- # Validate API key and get user_id
450
- user_id = await self.database.validate_api_key(api_key)
451
- if not user_id:
452
- logger.warning("Invalid API key provided to webhook")
453
- raise HTTPException(
454
- status_code=401,
455
- detail="Invalid API key"
456
- )
457
411
 
458
412
  # Parse request data
459
413
  if request.method == "POST":
@@ -465,108 +419,35 @@ class TriggerServer:
465
419
  payload = {"raw_body": body.decode("utf-8") if body else ""}
466
420
  else:
467
421
  payload = dict(request.query_params)
468
-
469
- # Step 2: Registration resolution
470
- if not trigger.registration_resolver:
471
- raise HTTPException(
472
- status_code=500,
473
- detail=f"Trigger {trigger.id} missing required registration_resolver"
474
- )
475
-
476
- # Extract resource identifiers from webhook payload
477
- resource_data = await trigger.registration_resolver(payload)
478
-
479
- # Find matching registration for the authenticated user
480
- # Convert Pydantic model to dict for database lookup
481
- resource_dict = resource_data.model_dump()
482
- registration = await self.database.find_registration_by_resource(
483
- trigger.id,
484
- resource_dict,
485
- user_id
486
- )
487
422
 
488
- if not registration:
489
- logger.warning(f"No registration found for user {user_id}, trigger_id={trigger.id} with resource={resource_data}")
490
- raise HTTPException(
491
- status_code=400,
492
- detail=f"No registration found for {trigger.id} with resource {resource_data}"
493
- )
494
-
495
- # Step 3: Inject OAuth tokens if needed
496
- auth_user = None
497
- if trigger.oauth_providers and self.langchain_auth_client:
498
- try:
499
- auth_user = await self._get_authenticated_user(trigger, user_id)
500
-
501
- # Check if any provider requires re-authentication - this shouldn't happen in webhooks
502
- for provider in trigger.oauth_providers.keys():
503
- provider_info = auth_user.providers.get(provider)
504
- if provider_info and provider_info.auth_required:
505
- logger.error(f"User {user_id} needs to re-authenticate for {provider} - this should have been handled during registration")
506
- return {
507
- "success": False,
508
- "error": f"Authentication required for {provider}",
509
- "message": "User needs to re-authenticate this trigger"
510
- }
511
-
512
- except Exception as e:
513
- logger.error(f"OAuth authentication failed: {e}")
514
- # Continue without auth - triggers can handle missing tokens
515
-
516
- # Step 4: Create metadata manager
517
- metadata_manager = MetadataManager(
518
- database=self.database,
519
- registration_id=registration["id"],
520
- initial_metadata=registration.get("metadata", {})
521
- )
522
-
523
- # Step 5: Call handler with parsed registration data
524
- result = await trigger.trigger_handler(payload, auth_user, metadata_manager)
525
- registration_id = registration["id"]
526
-
527
- # Check if we should invoke agents
423
+ query_params = dict(request.query_params)
424
+ result = await trigger.trigger_handler(payload, query_params, self.database, self.langchain_auth_client)
528
425
  if not result.invoke_agent:
529
- logger.info(f"Handler requested no agent invocation for registration {registration_id}")
530
- return {
531
- "success": True,
532
- "agents_invoked": 0
533
- }
534
-
535
- # Get agents linked to this trigger registration
426
+ return result.response_body
427
+
428
+ registration_id = result.registration["id"]
536
429
  agent_links = await self.database.get_agents_for_trigger(registration_id)
537
-
538
- if not agent_links:
539
- logger.info(f"No agents linked to registration {registration_id}")
540
- return {
541
- "success": True,
542
- "agents_invoked": 0
543
- }
544
-
545
- logger.info(f"Processing trigger result for registration {registration_id} with {len(agent_links)} linked agents")
546
-
547
- # Invoke each linked agent
430
+
548
431
  agents_invoked = 0
549
432
  for agent_link in agent_links:
550
433
  agent_id = agent_link if isinstance(agent_link, str) else agent_link.get("agent_id")
551
-
552
- # Use the data string from TriggerHandlerResult directly
434
+
553
435
  agent_input = {
554
436
  "messages": [
555
- {"role": "human", "content": result.data}
437
+ {"role": "human", "content": result.agent_message}
556
438
  ]
557
439
  }
558
440
 
559
441
  try:
560
442
  success = await self._invoke_agent(
561
443
  agent_id=agent_id,
562
- user_id=registration["user_id"],
444
+ user_id=result.registration["user_id"],
563
445
  input_data=agent_input,
564
446
  )
565
447
  if success:
566
448
  agents_invoked += 1
567
449
  except Exception as e:
568
450
  logger.error(f"Error invoking agent {agent_id}: {e}", exc_info=True)
569
-
570
451
  logger.info(f"Processed trigger handler, invoked {agents_invoked} agents")
571
452
 
572
453
  return {
@@ -594,12 +475,10 @@ class TriggerServer:
594
475
  logger.info(f"Invoking LangGraph agent {agent_id} for user {user_id}")
595
476
 
596
477
  try:
597
- # Build headers using the custom function
598
- headers = await self.langgraph_headers_builder(
599
- user_id=user_id,
600
- api_key=self.langgraph_api_key,
601
- agent_id=agent_id
602
- )
478
+ headers = {
479
+ "x-auth-scheme": "oap-trigger",
480
+ "x-supabase-user-id": user_id,
481
+ }
603
482
 
604
483
  thread = await self.langgraph_client.threads.create(
605
484
  metadata={
@@ -633,41 +512,6 @@ class TriggerServer:
633
512
  logger.error(f"Error invoking agent {agent_id}: {e}")
634
513
  raise
635
514
 
636
- async def _get_authenticated_user(self, trigger: TriggerTemplate, user_id: str) -> UserAuthInfo:
637
- """Get authenticated user with OAuth tokens for the trigger's required providers."""
638
- providers = {}
639
-
640
- # Get tokens for each required OAuth provider
641
- for provider, scopes in trigger.oauth_providers.items():
642
- try:
643
- auth_result = await self.langchain_auth_client.authenticate(
644
- provider=provider,
645
- scopes=scopes,
646
- user_id=user_id
647
- )
648
-
649
- # Debug logging
650
- logger.info(f"Auth result for {provider}: {vars(auth_result) if hasattr(auth_result, '__dict__') else 'Not available'}")
651
-
652
- if hasattr(auth_result, 'token') and auth_result.token:
653
- providers[provider] = ProviderAuthInfo(token=auth_result.token)
654
- logger.debug(f"Successfully got {provider} token for user {user_id}")
655
- elif hasattr(auth_result, 'auth_required') and auth_result.auth_required:
656
- logger.info(f"User {user_id} needs to authenticate for {provider}")
657
- providers[provider] = ProviderAuthInfo(
658
- auth_required=True,
659
- auth_url=getattr(auth_result, 'auth_url', None),
660
- auth_id=getattr(auth_result, 'auth_id', None)
661
- )
662
- else:
663
- logger.warning(f"No token returned for {provider} provider")
664
-
665
- except Exception as e:
666
- logger.error(f"Failed to get {provider} token: {e}")
667
- # Continue with other providers
668
-
669
- return UserAuthInfo(user_id=user_id, providers=providers)
670
-
671
515
  def get_app(self) -> FastAPI:
672
516
  """Get the FastAPI app instance."""
673
517
  return self.app
@@ -0,0 +1,78 @@
1
+ """Core types and interfaces for the triggers framework."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any, Dict, Optional
7
+ from pydantic import BaseModel, Field
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+
13
+ class ProviderAuthInfo(BaseModel):
14
+ """Authentication info for a specific OAuth provider."""
15
+
16
+ token: Optional[str] = None
17
+ auth_required: bool = False
18
+ auth_url: Optional[str] = None
19
+ auth_id: Optional[str] = None
20
+
21
+
22
+ class UserAuthInfo(BaseModel):
23
+ """User authentication info containing OAuth tokens or auth requirements."""
24
+
25
+ user_id: str
26
+ providers: Dict[str, ProviderAuthInfo] = Field(default_factory=dict)
27
+
28
+ class Config:
29
+ arbitrary_types_allowed = True
30
+
31
+
32
+
33
+
34
+ class AgentInvocationRequest(BaseModel):
35
+ """Request to invoke an AI agent."""
36
+
37
+ assistant_id: str
38
+ user_id: str
39
+ input_data: Any
40
+ thread_id: Optional[str] = None
41
+ metadata: Dict[str, Any] = Field(default_factory=dict)
42
+
43
+
44
+ class TriggerHandlerResult(BaseModel):
45
+ """Result returned by trigger handlers."""
46
+ invoke_agent: bool = Field(default=True, description="Whether to invoke agents for this event")
47
+ agent_message: Optional[str] = Field(default=None, description="String message to send to agents")
48
+ response_body: Optional[Dict[str, Any]] = Field(default=None, description="Custom HTTP response body (when invoke_agent=False)")
49
+ registration: Optional[Dict[str, Any]] = Field(default=None, description="Registration data (required when invoke_agent=True)")
50
+
51
+ def model_post_init(self, __context) -> None:
52
+ """Validate that required fields are provided based on invoke_agent."""
53
+ if self.invoke_agent and not self.agent_message:
54
+ raise ValueError("agent_message is required when invoke_agent=True")
55
+ if self.invoke_agent and not self.registration:
56
+ raise ValueError("registration is required when invoke_agent=True")
57
+ if not self.invoke_agent and not self.response_body:
58
+ raise ValueError("response_body is required when invoke_agent=False")
59
+
60
+
61
+ class TriggerRegistrationResult(BaseModel):
62
+ """Result returned by registration handlers."""
63
+ create_registration: bool = Field(default=True, description="Whether to create database registration (False = return custom response)")
64
+ metadata: Dict[str, Any] = Field(default_factory=dict, description="Metadata to store with the registration")
65
+ response_body: Optional[Dict[str, Any]] = Field(default=None, description="Custom HTTP response body (when create_registration=False)")
66
+ status_code: Optional[int] = Field(default=None, description="HTTP status code (when create_registration=False)")
67
+
68
+ def model_post_init(self, __context) -> None:
69
+ """Validate that required fields are provided based on create_registration."""
70
+ if self.create_registration and not self.metadata:
71
+ self.metadata = {} # Allow empty metadata for create_registration=True
72
+ if not self.create_registration and (not self.response_body or not self.status_code):
73
+ raise ValueError("Both response_body and status_code are required when create_registration=False")
74
+
75
+
76
+ class TriggerRegistrationModel(BaseModel):
77
+ """Base class for trigger resource models that define how webhooks are matched to registrations."""
78
+ pass
@@ -61,10 +61,14 @@ class TriggerDatabaseInterface(ABC):
61
61
  async def find_registration_by_resource(
62
62
  self,
63
63
  template_id: str,
64
- resource_data: Dict[str, Any],
65
- user_id: str
64
+ resource_data: Dict[str, Any]
66
65
  ) -> Optional[Dict[str, Any]]:
67
- """Find trigger registration by matching resource data for a specific user."""
66
+ """Find trigger registration by matching resource data."""
67
+ pass
68
+
69
+ @abstractmethod
70
+ async def get_all_registrations(self, template_id: str) -> List[Dict[str, Any]]:
71
+ """Get all registrations for a specific trigger template."""
68
72
  pass
69
73
 
70
74
  @abstractmethod
@@ -130,7 +134,3 @@ class TriggerDatabaseInterface(ABC):
130
134
  """Get user ID by email from trigger registrations."""
131
135
  pass
132
136
 
133
- @abstractmethod
134
- async def validate_api_key(self, api_key: str) -> Optional[str]:
135
- """Validate API key and return user_id if valid, None if invalid."""
136
- pass
@@ -143,7 +143,7 @@ class SupabaseTriggerDatabase(TriggerDatabaseInterface):
143
143
  return response.data[0] if response.data else None
144
144
 
145
145
  except Exception as e:
146
- logger.error(f"Error creating trigger registration: {e}")
146
+ logger.exception(f"Error creating trigger registration: {e}")
147
147
  return None
148
148
 
149
149
  async def get_user_trigger_registrations(self, user_id: str) -> List[Dict[str, Any]]:
@@ -225,15 +225,14 @@ class SupabaseTriggerDatabase(TriggerDatabaseInterface):
225
225
  async def find_registration_by_resource(
226
226
  self,
227
227
  template_id: str,
228
- resource_data: Dict[str, Any],
229
- user_id: str
228
+ resource_data: Dict[str, Any]
230
229
  ) -> Optional[Dict[str, Any]]:
231
- """Find trigger registration by matching resource data for a specific user."""
230
+ """Find trigger registration by matching resource data."""
232
231
  try:
233
- # Build query to match against trigger_registrations with template_id and user_id filters
232
+ # Build query to match against trigger_registrations with template_id filter
234
233
  query = self.client.table("trigger_registrations").select(
235
234
  "*, trigger_templates(id, name, description)"
236
- ).eq("trigger_templates.id", template_id).eq("user_id", user_id)
235
+ ).eq("trigger_templates.id", template_id)
237
236
 
238
237
  # Add resource field matches
239
238
  for field, value in resource_data.items():
@@ -249,6 +248,18 @@ class SupabaseTriggerDatabase(TriggerDatabaseInterface):
249
248
  logger.error(f"Error finding registration by resource: {e}")
250
249
  return None
251
250
 
251
+ async def get_all_registrations(self, template_id: str) -> List[Dict[str, Any]]:
252
+ """Get all registrations for a specific trigger template."""
253
+ try:
254
+ response = self.client.table("trigger_registrations").select(
255
+ "*, trigger_templates(id, name, description)"
256
+ ).eq("trigger_templates.id", template_id).execute()
257
+
258
+ return response.data or []
259
+ except Exception as e:
260
+ logger.error(f"Error getting all registrations for template {template_id}: {e}")
261
+ return []
262
+
252
263
  # ========== Agent-Trigger Links ==========
253
264
 
254
265
  async def link_agent_to_trigger(
@@ -346,30 +357,3 @@ class SupabaseTriggerDatabase(TriggerDatabaseInterface):
346
357
  logger.error(f"Error getting user by email: {e}")
347
358
  return None
348
359
 
349
- async def validate_api_key(self, api_key: str) -> Optional[str]:
350
- """Validate API key and return user_id if valid, None if invalid."""
351
- try:
352
- # Query all user API keys to find a match
353
- response = self.client.table("user_api_keys").select("user_id, key_hash").execute()
354
-
355
- if not response.data:
356
- return None
357
-
358
- # Check each encrypted key to see if it matches the provided key
359
- for row in response.data:
360
- try:
361
- decrypted_key = self._decrypt_secret(row["key_hash"])
362
- if decrypted_key == api_key:
363
- logger.info(f"Valid API key authenticated for user: {row['user_id']}")
364
- return row["user_id"]
365
- except Exception as e:
366
- # Skip keys that fail to decrypt
367
- logger.debug(f"Failed to decrypt API key: {e}")
368
- continue
369
-
370
- logger.warning(f"Invalid API key provided")
371
- return None
372
-
373
- except Exception as e:
374
- logger.error(f"Error validating API key: {e}")
375
- return None
@@ -3,8 +3,9 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import inspect
6
- from typing import Any, Awaitable, Callable, Dict, List, Optional, Type, get_type_hints
7
- from .core import UserAuthInfo, TriggerRegistrationModel, TriggerHandlerResult, TriggerRegistrationResult, MetadataManager
6
+ from typing import Any, Dict, List, Type, get_type_hints
7
+ from langchain_auth.client import Client
8
+ from .core import TriggerHandlerResult, TriggerRegistrationResult
8
9
  from pydantic import BaseModel
9
10
 
10
11
  class TriggerTemplate:
@@ -19,9 +20,6 @@ class TriggerTemplate:
19
20
 
20
21
  registration_handler,
21
22
  trigger_handler,
22
- registration_resolver,
23
-
24
- oauth_providers: Optional[Dict[str, List[str]]] = None,
25
23
  ):
26
24
  self.id = id
27
25
  self.name = name
@@ -29,21 +27,17 @@ class TriggerTemplate:
29
27
  self.registration_model = registration_model
30
28
  self.registration_handler = registration_handler
31
29
  self.trigger_handler = trigger_handler
32
- self.registration_resolver = registration_resolver
33
- self.oauth_providers = oauth_providers or {}
34
30
 
35
31
  self._validate_handler_signatures()
36
32
 
37
33
  def _validate_handler_signatures(self):
38
34
  """Validate that all handler functions have the correct signatures."""
39
- # Expected: async def handler(registration: RegistrationModel, auth_user: UserAuthInfo) -> TriggerRegistrationResult
40
- self._validate_handler("registration_handler", self.registration_handler, [self.registration_model, UserAuthInfo], TriggerRegistrationResult)
35
+ # Expected: async def handler(user_id: str, auth_client: Client, registration: RegistrationModel) -> TriggerRegistrationResult
36
+ self._validate_handler("registration_handler", self.registration_handler, [str, Client, self.registration_model], TriggerRegistrationResult)
41
37
 
42
- # Expected: async def handler(payload: Dict[str, Any], auth_user: UserAuthInfo, metadata: MetadataManager) -> TriggerHandlerResult
43
- self._validate_handler("trigger_handler", self.trigger_handler, [Dict[str, Any], UserAuthInfo, MetadataManager], TriggerHandlerResult)
38
+ # Expected: async def handler(payload: Dict[str, Any], query_params: Dict[str, str], database, auth_client: Client) -> TriggerHandlerResult
39
+ self._validate_handler("trigger_handler", self.trigger_handler, [Dict[str, Any], Dict[str, str], Any, Client], TriggerHandlerResult)
44
40
 
45
- # Expected: async def resolver(payload: Dict[str, Any]) -> RegistrationModel
46
- self._validate_handler("registration_resolver", self.registration_resolver, [Dict[str, Any]], self.registration_model)
47
41
 
48
42
  def _validate_handler(self, handler_name: str, handler_func, expected_types: List[Type], expected_return_type: Type = None):
49
43
  """Common validation logic for all handler functions."""
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "langchain-trigger-server"
7
- version = "0.1.7"
7
+ version = "0.1.9"
8
8
  description = "Generic event-driven triggers framework"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,83 +0,0 @@
1
- """Core types and interfaces for the triggers framework."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import Any, Dict, Optional
6
- from pydantic import BaseModel, Field
7
-
8
-
9
-
10
- class ProviderAuthInfo(BaseModel):
11
- """Authentication info for a specific OAuth provider."""
12
-
13
- token: Optional[str] = None
14
- auth_required: bool = False
15
- auth_url: Optional[str] = None
16
- auth_id: Optional[str] = None
17
-
18
-
19
- class UserAuthInfo(BaseModel):
20
- """User authentication info containing OAuth tokens or auth requirements."""
21
-
22
- user_id: str
23
- providers: Dict[str, ProviderAuthInfo] = Field(default_factory=dict)
24
-
25
- class Config:
26
- arbitrary_types_allowed = True
27
-
28
-
29
- class MetadataManager:
30
- """Manages trigger registration metadata with database persistence."""
31
-
32
- def __init__(self, database: Any, registration_id: str, initial_metadata: Dict[str, Any]):
33
- self.database = database
34
- self.registration_id = registration_id
35
- self.metadata = initial_metadata.copy()
36
-
37
- def get(self, key: str, default: Any = None) -> Any:
38
- """Get a metadata value by key."""
39
- return self.metadata.get(key, default)
40
-
41
- async def update(self, updates: Dict[str, Any]) -> None:
42
- """Update metadata and persist to database."""
43
- # Update local state
44
- self.metadata.update(updates)
45
-
46
- # Persist to database
47
- await self.database.update_trigger_metadata(self.registration_id, updates)
48
-
49
-
50
-
51
- class AgentInvocationRequest(BaseModel):
52
- """Request to invoke an AI agent."""
53
-
54
- assistant_id: str
55
- user_id: str
56
- input_data: Any
57
- thread_id: Optional[str] = None
58
- metadata: Dict[str, Any] = Field(default_factory=dict)
59
-
60
-
61
- class TriggerHandlerResult(BaseModel):
62
- """Result returned by trigger handlers."""
63
-
64
- invoke_agent: bool = Field(default=True, description="Whether to invoke agents for this event")
65
- data: Optional[str] = Field(default=None, description="String data to send to agents")
66
-
67
- def model_post_init(self, __context) -> None:
68
- """Validate that data is provided when invoke_agent is True."""
69
- if self.invoke_agent and not self.data:
70
- raise ValueError("data field is required when invoke_agent is True")
71
-
72
-
73
- class TriggerRegistrationResult(BaseModel):
74
- """Result returned by registration handlers."""
75
-
76
- metadata: Dict[str, Any] = Field(default_factory=dict, description="Metadata to store with the registration")
77
-
78
-
79
- class TriggerRegistrationModel(BaseModel):
80
- """Base class for trigger resource models that define how webhooks are matched to registrations."""
81
-
82
- class Config:
83
- arbitrary_types_allowed = True