langchain-trigger-server 0.3.7__tar.gz → 0.3.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.

Files changed (29) hide show
  1. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/PKG-INFO +1 -1
  2. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/langchain_triggers/app.py +10 -33
  3. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/langchain_triggers/database/interface.py +0 -24
  4. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/langchain_triggers/decorators.py +20 -0
  5. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/langchain_triggers/triggers/cron_trigger.py +29 -3
  6. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/pyproject.toml +1 -1
  7. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/tests/unit/test_trigger_server_api.py +16 -25
  8. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/uv.lock +1 -1
  9. langchain_trigger_server-0.3.9/version_comparison.txt +1 -0
  10. langchain_trigger_server-0.3.7/version_comparison.txt +0 -1
  11. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/.github/actions/uv_setup/action.yml +0 -0
  12. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/.github/workflows/_lint.yml +0 -0
  13. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/.github/workflows/_test.yml +0 -0
  14. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/.github/workflows/ci.yml +0 -0
  15. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/.github/workflows/release.yml +0 -0
  16. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/.gitignore +0 -0
  17. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/Makefile +0 -0
  18. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/README.md +0 -0
  19. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/langchain_triggers/__init__.py +0 -0
  20. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/langchain_triggers/auth/__init__.py +0 -0
  21. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/langchain_triggers/core.py +0 -0
  22. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/langchain_triggers/cron_manager.py +0 -0
  23. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/langchain_triggers/database/__init__.py +0 -0
  24. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/langchain_triggers/triggers/__init__.py +0 -0
  25. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/langchain_triggers/util.py +0 -0
  26. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/tests/__init__.py +0 -0
  27. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/tests/unit/__init__.py +0 -0
  28. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/tests/unit/test_cron_manager_polling_filter.py +0 -0
  29. {langchain_trigger_server-0.3.7 → langchain_trigger_server-0.3.9}/tests/unit/test_cron_manager_schedule_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langchain-trigger-server
3
- Version: 0.3.7
3
+ Version: 0.3.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
@@ -157,7 +157,6 @@ class TriggerServer:
157
157
  # Add startup and shutdown events
158
158
  @self.app.on_event("startup")
159
159
  async def startup_event():
160
- await self.ensure_trigger_templates()
161
160
  await self.cron_manager.start()
162
161
 
163
162
  @self.app.on_event("shutdown")
@@ -210,29 +209,6 @@ class TriggerServer:
210
209
  f"Registered trigger template in memory: {trigger.name} ({trigger.id})"
211
210
  )
212
211
 
213
- async def ensure_trigger_templates(self) -> None:
214
- """Ensure all registered trigger templates exist in the database."""
215
- for trigger in self.triggers:
216
- existing = await self.database.get_trigger_template(trigger.id)
217
- if not existing:
218
- logger.info(
219
- f"Creating new trigger template in database: {trigger.name} ({trigger.id})"
220
- )
221
- await self.database.create_trigger_template(
222
- id=trigger.id,
223
- provider=trigger.provider,
224
- name=trigger.name,
225
- description=trigger.description,
226
- registration_schema=trigger.registration_model.model_json_schema(),
227
- )
228
- logger.info(
229
- f"✓ Successfully created trigger template: {trigger.name} ({trigger.id})"
230
- )
231
- else:
232
- logger.info(
233
- f"✓ Trigger template already exists in database: {trigger.name} ({trigger.id})"
234
- )
235
-
236
212
  def add_triggers(self, triggers: list[TriggerTemplate]) -> None:
237
213
  """Add multiple triggers."""
238
214
  for trigger in triggers:
@@ -251,19 +227,20 @@ class TriggerServer:
251
227
 
252
228
  @self.app.get("/v1/triggers")
253
229
  async def api_list_triggers() -> dict[str, Any]:
254
- """List available trigger templates."""
255
- templates = await self.database.get_trigger_templates()
230
+ """List available trigger templates from in-memory registry."""
256
231
  trigger_list = []
257
- for template in templates:
232
+ for trigger in self.triggers:
258
233
  trigger_list.append(
259
234
  {
260
- "id": template["id"],
261
- "provider": template["provider"],
262
- "displayName": template["name"],
263
- "description": template["description"],
235
+ "id": trigger.id,
236
+ "provider": trigger.provider,
237
+ "displayName": trigger.name,
238
+ "description": trigger.description,
264
239
  "path": "/v1/triggers/registrations",
265
240
  "method": "POST",
266
- "payloadSchema": template.get("registration_schema", {}),
241
+ "payloadSchema": trigger.registration_model.model_json_schema(),
242
+ "display_name": trigger.display_name,
243
+ "integration": trigger.integration,
267
244
  }
268
245
  )
269
246
 
@@ -292,7 +269,7 @@ class TriggerServer:
292
269
  {
293
270
  "id": reg["id"],
294
271
  "user_id": reg["user_id"],
295
- "template_id": reg.get("trigger_templates", {}).get("id"),
272
+ "template_id": reg.get("template_id"),
296
273
  "resource": reg["resource"],
297
274
  "linked_agent_ids": reg.get("linked_agent_ids", []),
298
275
  "created_at": reg["created_at"],
@@ -7,30 +7,6 @@ from typing import Any
7
7
  class TriggerDatabaseInterface(ABC):
8
8
  """Abstract interface for trigger database operations."""
9
9
 
10
- # ========== Trigger Templates ==========
11
-
12
- @abstractmethod
13
- async def create_trigger_template(
14
- self,
15
- id: str,
16
- provider: str,
17
- name: str,
18
- description: str = None,
19
- registration_schema: dict = None,
20
- ) -> dict[str, Any] | None:
21
- """Create a new trigger template."""
22
- pass
23
-
24
- @abstractmethod
25
- async def get_trigger_templates(self) -> list[dict[str, Any]]:
26
- """Get all available trigger templates."""
27
- pass
28
-
29
- @abstractmethod
30
- async def get_trigger_template(self, id: str) -> dict[str, Any] | None:
31
- """Get a specific trigger template by ID."""
32
- pass
33
-
34
10
  # ========== Trigger Registrations ==========
35
11
 
36
12
  @abstractmethod
@@ -31,7 +31,25 @@ class TriggerTemplate:
31
31
  trigger_type: TriggerType = TriggerType.WEBHOOK,
32
32
  poll_handler: Any | None = None,
33
33
  default_crontab: str | None = None,
34
+ display_name: str | None = None,
35
+ integration: str | None = None,
34
36
  ):
37
+ """Initialize a trigger template.
38
+
39
+ Args:
40
+ id: Unique identifier for the trigger
41
+ provider: (DEPRECATED: use display_name) Display name for grouping in UI
42
+ name: Human-readable name for the trigger
43
+ description: Description of what the trigger does
44
+ registration_model: Pydantic model for registration data
45
+ registration_handler: Async function to handle registration
46
+ trigger_handler: Async function to handle webhook events (for webhook triggers)
47
+ trigger_type: Type of trigger (webhook or polling)
48
+ poll_handler: Async function to handle polling (for polling triggers)
49
+ default_crontab: Default cron schedule for polling triggers
50
+ display_name: Display name for grouping triggers (e.g., "Slack", "Gmail")
51
+ integration: Integration ID for logo mapping (e.g., "slack", "gmail")
52
+ """
35
53
  self.id = id
36
54
  self.provider = provider
37
55
  self.name = name
@@ -42,6 +60,8 @@ class TriggerTemplate:
42
60
  self.trigger_type = trigger_type
43
61
  self.poll_handler = poll_handler
44
62
  self.default_crontab = default_crontab
63
+ self.display_name = display_name
64
+ self.integration = integration
45
65
 
46
66
  self._validate_handler_signatures()
47
67
 
@@ -1,6 +1,7 @@
1
1
  """Cron-based trigger for scheduled agent execution."""
2
2
 
3
3
  import logging
4
+ import uuid
4
5
  from datetime import datetime
5
6
  from typing import Any
6
7
 
@@ -85,11 +86,14 @@ async def cron_poll_handler(
85
86
  tenant_id = str(registration.get("tenant_id", ""))
86
87
 
87
88
  agent_links = await database.get_agents_for_trigger(registration_id)
89
+
88
90
  if not agent_links:
89
91
  logger.info(f"cron_no_linked_agents registration_id={registration_id}")
90
92
  return {"success": True, "message": "No linked agents", "agents_invoked": 0}
91
93
 
92
- client = get_client(url=get_langgraph_url(), api_key=None)
94
+ langgraph_url = get_langgraph_url()
95
+
96
+ client = get_client(url=langgraph_url, api_key=None)
93
97
  headers = create_service_auth_headers(user_id, tenant_id)
94
98
 
95
99
  current_time = datetime.utcnow()
@@ -100,9 +104,29 @@ async def cron_poll_handler(
100
104
  agent_id = str(
101
105
  agent_link if isinstance(agent_link, str) else agent_link.get("agent_id")
102
106
  )
107
+
103
108
  try:
109
+ thread_id = str(uuid.uuid4())
110
+
111
+ try:
112
+ await client.threads.create(
113
+ thread_id=thread_id,
114
+ if_exists="do_nothing",
115
+ metadata={
116
+ "triggered_by": "cron-trigger",
117
+ "user_id": user_id,
118
+ "tenant_id": tenant_id,
119
+ "registration_id": str(registration_id),
120
+ },
121
+ headers=headers,
122
+ )
123
+ except Exception as thread_err:
124
+ logger.warning(
125
+ f"cron_thread_create_failed thread_id={thread_id} error={str(thread_err)}"
126
+ )
127
+
104
128
  await client.runs.create(
105
- None,
129
+ thread_id,
106
130
  agent_id,
107
131
  input={
108
132
  "messages": [
@@ -115,7 +139,7 @@ async def cron_poll_handler(
115
139
  headers=headers,
116
140
  )
117
141
  logger.info(
118
- f"cron_run_ok registration_id={registration_id} agent_id={agent_id}"
142
+ f"cron_run_ok registration_id={registration_id} agent_id={agent_id} thread_id={thread_id}"
119
143
  )
120
144
  agents_invoked += 1
121
145
  except Exception as e:
@@ -135,4 +159,6 @@ cron_trigger = TriggerTemplate(
135
159
  registration_handler=cron_registration_handler,
136
160
  trigger_type=TriggerType.POLLING,
137
161
  poll_handler=cron_poll_handler,
162
+ display_name="Cron",
163
+ integration=None,
138
164
  )
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "langchain-trigger-server"
7
- version = "0.3.7"
7
+ version = "0.3.9"
8
8
  description = "Generic event-driven triggers framework"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -40,29 +40,9 @@ class MockDatabase:
40
40
  """Mock database for testing."""
41
41
 
42
42
  def __init__(self):
43
- self.templates = []
44
43
  self.registrations = []
45
44
  self.agent_links = []
46
45
 
47
- async def create_trigger_template(
48
- self, id, provider, name, description, registration_schema
49
- ):
50
- template = {
51
- "id": id,
52
- "provider": provider,
53
- "name": name,
54
- "description": description,
55
- "registration_schema": registration_schema,
56
- }
57
- self.templates.append(template)
58
- return template
59
-
60
- async def get_trigger_template(self, id):
61
- return next((t for t in self.templates if t["id"] == id), None)
62
-
63
- async def get_trigger_templates(self):
64
- return self.templates
65
-
66
46
  async def create_trigger_registration(
67
47
  self, user_id, tenant_id, template_id, resource, metadata
68
48
  ):
@@ -83,7 +63,6 @@ class MockDatabase:
83
63
  return [
84
64
  {
85
65
  **reg,
86
- "trigger_templates": {"id": reg["template_id"]},
87
66
  "linked_agent_ids": reg.get("linked_agent_ids", []),
88
67
  }
89
68
  for reg in self.registrations
@@ -233,15 +212,27 @@ async def test_health_endpoint(trigger_server):
233
212
 
234
213
  @pytest.mark.asyncio
235
214
  async def test_list_triggers_endpoint(trigger_server):
236
- """Test listing trigger templates."""
237
- # Add a trigger template to the mock database
238
- await trigger_server.database.create_trigger_template(
215
+ """Test listing trigger templates from in-memory registry."""
216
+ from langchain_triggers import (
217
+ TriggerRegistrationResult,
218
+ TriggerTemplate,
219
+ )
220
+
221
+ # Create a test trigger registration handler
222
+ async def test_registration_handler(request, user_id, registration):
223
+ return TriggerRegistrationResult()
224
+
225
+ # Add a trigger template to the in-memory registry
226
+ test_trigger = TriggerTemplate(
239
227
  id="test_trigger",
240
228
  provider="test",
241
229
  name="Test Trigger",
242
230
  description="A test trigger",
243
- registration_schema={"type": "object"},
231
+ registration_model=TestRegistration,
232
+ registration_handler=test_registration_handler,
233
+ trigger_handler=dummy_trigger_handler,
244
234
  )
235
+ trigger_server.add_trigger(test_trigger)
245
236
 
246
237
  transport = ASGITransport(app=trigger_server.app, raise_app_exceptions=True)
247
238
  async with AsyncClient(base_url="http://localhost", transport=transport) as client:
@@ -381,7 +381,7 @@ wheels = [
381
381
 
382
382
  [[package]]
383
383
  name = "langchain-trigger-server"
384
- version = "0.3.7"
384
+ version = "0.3.9"
385
385
  source = { editable = "." }
386
386
  dependencies = [
387
387
  { name = "apscheduler" },
@@ -0,0 +1 @@
1
+ Current: 0.3.9, PyPI: 0.3.8, Should publish: True
@@ -1 +0,0 @@
1
- Current: 0.3.7, PyPI: 0.3.6, Should publish: True