langchain-trigger-server 0.3__py3-none-any.whl → 0.3.3__py3-none-any.whl
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.3.dist-info → langchain_trigger_server-0.3.3.dist-info}/METADATA +1 -1
- langchain_trigger_server-0.3.3.dist-info/RECORD +15 -0
- langchain_triggers/app.py +50 -5
- langchain_triggers/core.py +8 -0
- langchain_triggers/cron_manager.py +206 -70
- langchain_triggers/decorators.py +39 -12
- langchain_triggers/triggers/cron_trigger.py +20 -12
- langchain_trigger_server-0.3.dist-info/RECORD +0 -15
- {langchain_trigger_server-0.3.dist-info → langchain_trigger_server-0.3.3.dist-info}/WHEEL +0 -0
{langchain_trigger_server-0.3.dist-info → langchain_trigger_server-0.3.3.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langchain-trigger-server
|
|
3
|
-
Version: 0.3
|
|
3
|
+
Version: 0.3.3
|
|
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
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
langchain_triggers/__init__.py,sha256=WoW9LC_FJRs42mLWq2iuM-jjPow2Rue50q2zm56Oul0,536
|
|
2
|
+
langchain_triggers/app.py,sha256=-VytRXkAPI8qm-fbMJMbQ52Qt8WC2nKBgL_8FGJaFTM,40468
|
|
3
|
+
langchain_triggers/core.py,sha256=6r_91UWt7c26TVqkDxHc2xjmaQl42g-iFHGEWuDo0gs,3929
|
|
4
|
+
langchain_triggers/cron_manager.py,sha256=PueW_z7gdkSza9J05W1VV2OxsfqvodMo7ZlAQJKMvpI,16408
|
|
5
|
+
langchain_triggers/decorators.py,sha256=ZhWgUjbl2jUbvGSlqgThebd0tPgqXoQ02D7BcKZ9aIs,4675
|
|
6
|
+
langchain_triggers/auth/__init__.py,sha256=RtDKuBoKYuyHzLNpKr74cmALO0PhHlWO9Ho7k3CUYFE,349
|
|
7
|
+
langchain_triggers/auth/slack_hmac.py,sha256=kiwjhTXITgQvLAtEcOv8BnnWJRJcxaQ9dXkQm3JJDQ4,2948
|
|
8
|
+
langchain_triggers/database/__init__.py,sha256=B1I1qmVr3U1CSf0VkjxsL4W5QGda5T7uB_CsJq6yBF4,535
|
|
9
|
+
langchain_triggers/database/interface.py,sha256=jpADOOwcBQo1ZichgiZVaOvfZqEqVVo8Ea7ATWWTSBE,4283
|
|
10
|
+
langchain_triggers/database/supabase.py,sha256=zi_75GbqRvzzlXd5EgfYof4h6vHWOLS4I1759wvY9kQ,17009
|
|
11
|
+
langchain_triggers/triggers/__init__.py,sha256=Uw1544gxzN4XDRn2RzpZ5EAG6EAF38ZYQtVvlciEsMs,146
|
|
12
|
+
langchain_triggers/triggers/cron_trigger.py,sha256=YTscULtB8JesnB24IWZl0LDMkf-A9Y6dksAk7GLFJiQ,3355
|
|
13
|
+
langchain_trigger_server-0.3.3.dist-info/METADATA,sha256=lSxdTnBzgk9FQKnXa8BIvgfsLi-36RREMkfiVgQU1c0,1486
|
|
14
|
+
langchain_trigger_server-0.3.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
15
|
+
langchain_trigger_server-0.3.3.dist-info/RECORD,,
|
langchain_triggers/app.py
CHANGED
|
@@ -21,10 +21,10 @@ from .auth.slack_hmac import (
|
|
|
21
21
|
get_slack_signing_secret,
|
|
22
22
|
verify_slack_signature,
|
|
23
23
|
)
|
|
24
|
+
from .core import TriggerType
|
|
24
25
|
from .cron_manager import CronTriggerManager
|
|
25
26
|
from .database import TriggerDatabaseInterface, create_database
|
|
26
27
|
from .decorators import TriggerTemplate
|
|
27
|
-
from .triggers.cron_trigger import CRON_TRIGGER_ID
|
|
28
28
|
|
|
29
29
|
logger = logging.getLogger(__name__)
|
|
30
30
|
|
|
@@ -366,7 +366,7 @@ class TriggerServer:
|
|
|
366
366
|
detail=f"You already have a registration with this configuration for trigger type '{trigger.id}'. Registration ID: {existing_registration.get('id')}",
|
|
367
367
|
)
|
|
368
368
|
result = await trigger.registration_handler(
|
|
369
|
-
user_id, self.langchain_auth_client, registration_instance
|
|
369
|
+
request, user_id, self.langchain_auth_client, registration_instance
|
|
370
370
|
)
|
|
371
371
|
|
|
372
372
|
# Check if handler requested to skip registration (e.g., for OAuth or URL verification)
|
|
@@ -419,6 +419,29 @@ class TriggerServer:
|
|
|
419
419
|
logger.exception(f"Error creating trigger registration: {e}")
|
|
420
420
|
raise HTTPException(status_code=500, detail=str(e))
|
|
421
421
|
|
|
422
|
+
@self.app.delete("/v1/triggers/registrations/{registration_id}")
|
|
423
|
+
async def api_delete_registration(
|
|
424
|
+
registration_id: str,
|
|
425
|
+
current_user: dict[str, Any] = Depends(get_current_user),
|
|
426
|
+
) -> dict[str, Any]:
|
|
427
|
+
"""Delete a trigger registration."""
|
|
428
|
+
try:
|
|
429
|
+
user_id = current_user["identity"]
|
|
430
|
+
success = await self.database.delete_trigger_registration(
|
|
431
|
+
registration_id, user_id
|
|
432
|
+
)
|
|
433
|
+
if not success:
|
|
434
|
+
raise HTTPException(
|
|
435
|
+
status_code=500, detail="Failed to delete trigger registration"
|
|
436
|
+
)
|
|
437
|
+
return {"success": True}
|
|
438
|
+
|
|
439
|
+
except HTTPException:
|
|
440
|
+
raise
|
|
441
|
+
except Exception as e:
|
|
442
|
+
logger.error(f"Error deleting trigger registration: {e}")
|
|
443
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
444
|
+
|
|
422
445
|
@self.app.get("/v1/triggers/registrations/{registration_id}/agents")
|
|
423
446
|
async def api_list_registration_agents(
|
|
424
447
|
registration_id: str,
|
|
@@ -566,12 +589,34 @@ class TriggerServer:
|
|
|
566
589
|
detail="Trigger registration not found or access denied",
|
|
567
590
|
)
|
|
568
591
|
|
|
569
|
-
# Get the template to check if it's a
|
|
592
|
+
# Get the template to check if it's a polling trigger
|
|
570
593
|
template_id = registration.get("template_id")
|
|
571
|
-
|
|
594
|
+
tmpl = (
|
|
595
|
+
next((t for t in self.triggers if t.id == template_id), None)
|
|
596
|
+
if template_id
|
|
597
|
+
else None
|
|
598
|
+
)
|
|
599
|
+
if not template_id or not tmpl:
|
|
600
|
+
error_reason = (
|
|
601
|
+
"missing_template_id"
|
|
602
|
+
if not template_id
|
|
603
|
+
else "template_not_found"
|
|
604
|
+
)
|
|
605
|
+
logger.error(
|
|
606
|
+
"manual_execute_error registration_id=%s template_id=%s error=%s",
|
|
607
|
+
registration_id,
|
|
608
|
+
template_id,
|
|
609
|
+
error_reason,
|
|
610
|
+
stack_info=True,
|
|
611
|
+
)
|
|
612
|
+
raise HTTPException(status_code=500, detail="Internal server error")
|
|
613
|
+
if (
|
|
614
|
+
getattr(tmpl.trigger_type, "value", tmpl.trigger_type)
|
|
615
|
+
!= TriggerType.POLLING.value
|
|
616
|
+
):
|
|
572
617
|
raise HTTPException(
|
|
573
618
|
status_code=400,
|
|
574
|
-
detail="Manual execution is only supported for
|
|
619
|
+
detail="Manual execution is only supported for polling triggers",
|
|
575
620
|
)
|
|
576
621
|
|
|
577
622
|
# Execute the cron trigger using the cron manager
|
langchain_triggers/core.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
+
from enum import Enum
|
|
6
7
|
from typing import Any
|
|
7
8
|
|
|
8
9
|
from pydantic import BaseModel, Field
|
|
@@ -10,6 +11,13 @@ from pydantic import BaseModel, Field
|
|
|
10
11
|
logger = logging.getLogger(__name__)
|
|
11
12
|
|
|
12
13
|
|
|
14
|
+
class TriggerType(str, Enum):
|
|
15
|
+
"""Type of trigger supported by the framework."""
|
|
16
|
+
|
|
17
|
+
WEBHOOK = "webhook"
|
|
18
|
+
POLLING = "polling"
|
|
19
|
+
|
|
20
|
+
|
|
13
21
|
class ProviderAuthInfo(BaseModel):
|
|
14
22
|
"""Authentication info for a specific OAuth provider."""
|
|
15
23
|
|
|
@@ -8,7 +8,7 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
|
8
8
|
from apscheduler.triggers.cron import CronTrigger as APSCronTrigger
|
|
9
9
|
from pydantic import BaseModel
|
|
10
10
|
|
|
11
|
-
from langchain_triggers.
|
|
11
|
+
from langchain_triggers.core import TriggerHandlerResult, TriggerType
|
|
12
12
|
|
|
13
13
|
logger = logging.getLogger(__name__)
|
|
14
14
|
|
|
@@ -36,10 +36,19 @@ class CronTriggerManager:
|
|
|
36
36
|
self.execution_history = [] # Keep recent execution history
|
|
37
37
|
self.max_history = 1000
|
|
38
38
|
|
|
39
|
+
def _is_polling(self, trigger) -> bool:
|
|
40
|
+
ttype = getattr(trigger, "trigger_type", None)
|
|
41
|
+
val = getattr(ttype, "value", ttype)
|
|
42
|
+
try:
|
|
43
|
+
return str(val).lower() == TriggerType.POLLING.value
|
|
44
|
+
except Exception:
|
|
45
|
+
return False
|
|
46
|
+
|
|
39
47
|
async def start(self):
|
|
40
48
|
"""Start scheduler and load existing cron registrations."""
|
|
41
49
|
try:
|
|
42
50
|
self.scheduler.start()
|
|
51
|
+
logger.info("polling_manager_started timezone=UTC")
|
|
43
52
|
await self._load_existing_registrations()
|
|
44
53
|
except Exception as e:
|
|
45
54
|
logger.error(f"Failed to start CronTriggerManager: {e}")
|
|
@@ -53,25 +62,59 @@ class CronTriggerManager:
|
|
|
53
62
|
logger.error(f"Error shutting down CronTriggerManager: {e}")
|
|
54
63
|
|
|
55
64
|
async def _load_existing_registrations(self):
|
|
56
|
-
"""Load all existing
|
|
65
|
+
"""Load all existing polling registrations from database and schedule them.
|
|
66
|
+
|
|
67
|
+
Discovers polling-capable triggers dynamically from registered templates.
|
|
68
|
+
"""
|
|
57
69
|
try:
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
scheduled_total = 0
|
|
71
|
+
polling_templates = [
|
|
72
|
+
t for t in self.trigger_server.triggers if self._is_polling(t)
|
|
73
|
+
]
|
|
74
|
+
ids_csv = ",".join([t.id for t in polling_templates]) or ""
|
|
75
|
+
logger.info(
|
|
76
|
+
f"polling_templates_loaded count={len(polling_templates)} ids={ids_csv}"
|
|
60
77
|
)
|
|
61
78
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
for template in polling_templates:
|
|
80
|
+
template_id = template.id
|
|
81
|
+
logger.info(
|
|
82
|
+
"polling_template "
|
|
83
|
+
f"template_id={template_id} provider={(template.provider or '').lower()} name={getattr(template, 'name', '')}"
|
|
84
|
+
)
|
|
85
|
+
try:
|
|
86
|
+
registrations = (
|
|
87
|
+
await self.trigger_server.database.get_all_registrations(
|
|
88
|
+
template_id
|
|
71
89
|
)
|
|
72
|
-
|
|
90
|
+
)
|
|
91
|
+
logger.info(
|
|
92
|
+
f"registrations_fetched template_id={template_id} count={len(registrations)}"
|
|
93
|
+
)
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.error(
|
|
96
|
+
f"registrations_fetch_err template_id={template_id} error={str(e)}"
|
|
97
|
+
)
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
scheduled_for_template = 0
|
|
101
|
+
for registration in registrations:
|
|
102
|
+
if registration.get("status") == "active":
|
|
103
|
+
try:
|
|
104
|
+
await self._schedule_cron_job(registration)
|
|
105
|
+
scheduled_total += 1
|
|
106
|
+
scheduled_for_template += 1
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.error(
|
|
109
|
+
"registration_schedule_err "
|
|
110
|
+
f"registration_id={registration.get('id')} template_id={template_id} error={str(e)}"
|
|
111
|
+
)
|
|
112
|
+
logger.debug(
|
|
113
|
+
f"registrations_scheduled template_id={template_id} scheduled={scheduled_for_template}"
|
|
114
|
+
)
|
|
115
|
+
logger.debug(f"polling_schedule_complete total_scheduled={scheduled_total}")
|
|
73
116
|
except Exception as e:
|
|
74
|
-
logger.error(f"
|
|
117
|
+
logger.error(f"polling_load_err error={str(e)}")
|
|
75
118
|
|
|
76
119
|
async def reload_from_database(self):
|
|
77
120
|
"""Reload all cron registrations from database, replacing current schedules."""
|
|
@@ -88,15 +131,39 @@ class CronTriggerManager:
|
|
|
88
131
|
raise
|
|
89
132
|
|
|
90
133
|
async def on_registration_created(self, registration: dict[str, Any]):
|
|
91
|
-
"""Called when a new
|
|
92
|
-
|
|
134
|
+
"""Called when a new polling registration is created."""
|
|
135
|
+
template_id_raw = registration.get("template_id")
|
|
136
|
+
template_id = str(template_id_raw) if template_id_raw is not None else None
|
|
137
|
+
if template_id is None:
|
|
138
|
+
logger.error(
|
|
139
|
+
"registration_missing_template_id id=%s", registration.get("id")
|
|
140
|
+
)
|
|
141
|
+
return
|
|
142
|
+
tmpl = next(
|
|
143
|
+
(t for t in self.trigger_server.triggers if t.id == template_id), None
|
|
144
|
+
)
|
|
145
|
+
if not tmpl:
|
|
146
|
+
logger.error(
|
|
147
|
+
"registration_template_not_found id=%s template_id=%s",
|
|
148
|
+
registration.get("id"),
|
|
149
|
+
template_id,
|
|
150
|
+
)
|
|
151
|
+
return
|
|
152
|
+
if self._is_polling(tmpl):
|
|
93
153
|
try:
|
|
94
154
|
await self._schedule_cron_job(registration)
|
|
95
155
|
except Exception as e:
|
|
96
156
|
logger.error(
|
|
97
|
-
f"Failed to schedule new cron job {registration
|
|
157
|
+
f"Failed to schedule new cron job {registration.get('id')}: {e}"
|
|
98
158
|
)
|
|
99
159
|
raise
|
|
160
|
+
else:
|
|
161
|
+
logger.debug(
|
|
162
|
+
"registration_not_polling id=%s template_id=%s trigger_type=%s",
|
|
163
|
+
registration.get("id"),
|
|
164
|
+
template_id,
|
|
165
|
+
tmpl.trigger_type,
|
|
166
|
+
)
|
|
100
167
|
|
|
101
168
|
async def on_registration_deleted(self, registration_id: str):
|
|
102
169
|
"""Called when a cron registration is deleted."""
|
|
@@ -106,25 +173,36 @@ class CronTriggerManager:
|
|
|
106
173
|
logger.error(f"Failed to unschedule cron job {registration_id}: {e}")
|
|
107
174
|
|
|
108
175
|
async def _schedule_cron_job(self, registration: dict[str, Any]):
|
|
109
|
-
"""Add a
|
|
176
|
+
"""Add a polling job to the scheduler using a 5-field crontab."""
|
|
110
177
|
registration_id = registration["id"]
|
|
111
178
|
resource_data = registration.get("resource", {})
|
|
112
|
-
crontab = resource_data.get("crontab"
|
|
113
|
-
|
|
114
|
-
if not
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
179
|
+
crontab = (resource_data.get("crontab") or "").strip()
|
|
180
|
+
template_id = registration.get("template_id")
|
|
181
|
+
template_id = str(template_id) if template_id is not None else None
|
|
182
|
+
tmpl = next(
|
|
183
|
+
(t for t in self.trigger_server.triggers if t.id == template_id), None
|
|
184
|
+
)
|
|
185
|
+
if tmpl and not crontab:
|
|
186
|
+
crontab = (getattr(tmpl, "default_crontab", None) or "").strip()
|
|
118
187
|
|
|
119
188
|
try:
|
|
120
|
-
|
|
121
|
-
|
|
189
|
+
if not crontab:
|
|
190
|
+
if template_id is None:
|
|
191
|
+
raise ValueError(
|
|
192
|
+
f"No schedule provided for registration {registration_id} (missing template_id)"
|
|
193
|
+
)
|
|
194
|
+
if tmpl is None:
|
|
195
|
+
raise ValueError(
|
|
196
|
+
f"No schedule provided for registration {registration_id} (template '{template_id}' not found)"
|
|
197
|
+
)
|
|
198
|
+
raise ValueError(
|
|
199
|
+
f"No schedule provided for registration {registration_id} (no crontab and no default_crontab for template '{template_id}')"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
cron_parts = crontab.split()
|
|
122
203
|
if len(cron_parts) != 5:
|
|
123
204
|
raise ValueError(f"Invalid cron format: {crontab} (expected 5 parts)")
|
|
124
|
-
|
|
125
205
|
minute, hour, day, month, day_of_week = cron_parts
|
|
126
|
-
|
|
127
|
-
# Create APScheduler cron trigger
|
|
128
206
|
trigger = APSCronTrigger(
|
|
129
207
|
minute=minute,
|
|
130
208
|
hour=hour,
|
|
@@ -133,15 +211,18 @@ class CronTriggerManager:
|
|
|
133
211
|
day_of_week=day_of_week,
|
|
134
212
|
timezone="UTC",
|
|
135
213
|
)
|
|
214
|
+
job_id = f"cron_{registration_id}"
|
|
215
|
+
logger.info(
|
|
216
|
+
f"schedule_cron registration_id={registration_id} crontab='{crontab}' job_id={job_id}"
|
|
217
|
+
)
|
|
136
218
|
|
|
137
|
-
# Schedule the job
|
|
138
219
|
job = self.scheduler.add_job(
|
|
139
220
|
self._execute_cron_job_with_monitoring,
|
|
140
221
|
trigger=trigger,
|
|
141
222
|
args=[registration],
|
|
142
|
-
id=
|
|
143
|
-
name=f"
|
|
144
|
-
max_instances=1,
|
|
223
|
+
id=job_id,
|
|
224
|
+
name=f"Polling job for registration {registration_id}",
|
|
225
|
+
max_instances=1,
|
|
145
226
|
replace_existing=True,
|
|
146
227
|
)
|
|
147
228
|
|
|
@@ -149,7 +230,7 @@ class CronTriggerManager:
|
|
|
149
230
|
|
|
150
231
|
except Exception as e:
|
|
151
232
|
logger.error(
|
|
152
|
-
f"Failed to schedule
|
|
233
|
+
f"Failed to schedule polling job for registration {registration_id}: {e}"
|
|
153
234
|
)
|
|
154
235
|
raise
|
|
155
236
|
|
|
@@ -202,6 +283,8 @@ class CronTriggerManager:
|
|
|
202
283
|
"""Execute a cron job - invoke agents. Can be called manually or by scheduler."""
|
|
203
284
|
registration_id = registration["id"]
|
|
204
285
|
user_id = registration["user_id"]
|
|
286
|
+
template_id = registration.get("template_id")
|
|
287
|
+
template_id = str(template_id) if template_id is not None else None
|
|
205
288
|
tenant_id = (
|
|
206
289
|
registration.get("metadata", {}).get("client_metadata", {}).get("tenant_id")
|
|
207
290
|
)
|
|
@@ -215,45 +298,98 @@ class CronTriggerManager:
|
|
|
215
298
|
logger.warning(f"No agents linked to cron job {registration_id}")
|
|
216
299
|
return 0
|
|
217
300
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
301
|
+
tmpl = next(
|
|
302
|
+
(t for t in self.trigger_server.triggers if t.id == template_id), None
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
if not tmpl or not self._is_polling(tmpl):
|
|
306
|
+
available_ids = ",".join([t.id for t in self.trigger_server.triggers])
|
|
307
|
+
logger.error(
|
|
308
|
+
"template_not_polling "
|
|
309
|
+
f"template_id={template_id} available_templates={available_ids}"
|
|
224
310
|
)
|
|
225
|
-
|
|
226
|
-
agent_id_str = str(agent_id)
|
|
227
|
-
user_id_str = str(user_id)
|
|
228
|
-
tenant_id_str = str(tenant_id)
|
|
229
|
-
|
|
230
|
-
current_time = datetime.utcnow()
|
|
231
|
-
current_time_str = current_time.strftime("%A, %B %d, %Y at %H:%M UTC")
|
|
232
|
-
|
|
233
|
-
agent_input = {
|
|
234
|
-
"messages": [
|
|
235
|
-
{
|
|
236
|
-
"role": "human",
|
|
237
|
-
"content": f"ACTION: triggering cron from langchain-trigger-server\nCURRENT TIME: {current_time_str}",
|
|
238
|
-
}
|
|
239
|
-
]
|
|
240
|
-
}
|
|
311
|
+
return 0
|
|
241
312
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
input_data=agent_input,
|
|
248
|
-
)
|
|
249
|
-
if success:
|
|
250
|
-
agents_invoked += 1
|
|
313
|
+
result: TriggerHandlerResult = await tmpl.poll_handler(
|
|
314
|
+
registration,
|
|
315
|
+
self.trigger_server.database,
|
|
316
|
+
self.trigger_server.langchain_auth_client,
|
|
317
|
+
)
|
|
251
318
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
319
|
+
if not result.invoke_agent:
|
|
320
|
+
logger.info(
|
|
321
|
+
"poll_result "
|
|
322
|
+
f"registration_id={registration_id} "
|
|
323
|
+
f"trigger_id={template_id} "
|
|
324
|
+
f"provider={(tmpl.provider or '').lower()} "
|
|
325
|
+
f"invoke_agent=false messages_count=0"
|
|
326
|
+
)
|
|
327
|
+
return 0
|
|
328
|
+
|
|
329
|
+
agents_invoked = 0
|
|
330
|
+
messages = result.agent_messages or []
|
|
331
|
+
|
|
332
|
+
logger.info(
|
|
333
|
+
"poll_result "
|
|
334
|
+
f"registration_id={registration_id} "
|
|
335
|
+
f"trigger_id={template_id} "
|
|
336
|
+
f"provider={(tmpl.provider or '').lower()} "
|
|
337
|
+
f"invoke_agent=true messages_count={len(messages)} "
|
|
338
|
+
f"agents_linked={len(agent_links)}"
|
|
339
|
+
)
|
|
256
340
|
|
|
341
|
+
for message in messages:
|
|
342
|
+
for agent_link in agent_links:
|
|
343
|
+
agent_id = (
|
|
344
|
+
agent_link
|
|
345
|
+
if isinstance(agent_link, str)
|
|
346
|
+
else agent_link.get("agent_id")
|
|
347
|
+
)
|
|
348
|
+
# Ensure agent_id and user_id are strings for JSON serialization
|
|
349
|
+
agent_id_str = str(agent_id)
|
|
350
|
+
user_id_str = str(user_id)
|
|
351
|
+
tenant_id_str = str(tenant_id)
|
|
352
|
+
|
|
353
|
+
current_time = datetime.utcnow()
|
|
354
|
+
current_time_str = current_time.strftime("%A, %B %d, %Y at %H:%M UTC")
|
|
355
|
+
|
|
356
|
+
agent_input = {
|
|
357
|
+
"messages": [
|
|
358
|
+
{
|
|
359
|
+
"role": "human",
|
|
360
|
+
"content": f"ACTION: triggering cron from langchain-trigger-server\nCURRENT TIME: {current_time_str}\n\n{message}",
|
|
361
|
+
}
|
|
362
|
+
]
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
try:
|
|
366
|
+
success = await self.trigger_server._invoke_agent(
|
|
367
|
+
agent_id=agent_id_str,
|
|
368
|
+
user_id=user_id_str,
|
|
369
|
+
tenant_id=tenant_id_str,
|
|
370
|
+
input_data=agent_input,
|
|
371
|
+
)
|
|
372
|
+
if success:
|
|
373
|
+
logger.info(
|
|
374
|
+
"invoke_agent_ok "
|
|
375
|
+
f"registration_id={registration_id} "
|
|
376
|
+
f"agent_id={agent_id_str}"
|
|
377
|
+
)
|
|
378
|
+
agents_invoked += 1
|
|
379
|
+
except Exception as e:
|
|
380
|
+
logger.error(
|
|
381
|
+
"invoke_agent_err "
|
|
382
|
+
f"registration_id={registration_id} "
|
|
383
|
+
f"agent_id={agent_id_str} "
|
|
384
|
+
f"error={str(e)}"
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
logger.info(
|
|
388
|
+
"poll_invoke_summary "
|
|
389
|
+
f"registration_id={registration_id} "
|
|
390
|
+
f"agents_invoked={agents_invoked} "
|
|
391
|
+
f"messages_count={len(messages)}"
|
|
392
|
+
)
|
|
257
393
|
return agents_invoked
|
|
258
394
|
|
|
259
395
|
async def _record_execution(self, execution: CronJobExecution):
|
langchain_triggers/decorators.py
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
"""Trigger system - templates with registration and webhook handlers.
|
|
1
|
+
"""Trigger system - templates with registration and webhook handlers.
|
|
2
|
+
|
|
3
|
+
Also supports polling triggers (no HTTP route) via a `poll_handler` that the
|
|
4
|
+
framework scheduler can call on a cadence.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
from __future__ import annotations
|
|
4
8
|
|
|
5
9
|
import inspect
|
|
6
10
|
from typing import Any, get_type_hints
|
|
7
11
|
|
|
12
|
+
from fastapi import Request
|
|
8
13
|
from langchain_auth.client import Client
|
|
9
14
|
from pydantic import BaseModel
|
|
10
15
|
|
|
11
|
-
from .core import TriggerHandlerResult, TriggerRegistrationResult
|
|
16
|
+
from .core import TriggerHandlerResult, TriggerRegistrationResult, TriggerType
|
|
12
17
|
|
|
13
18
|
|
|
14
19
|
class TriggerTemplate:
|
|
@@ -22,7 +27,11 @@ class TriggerTemplate:
|
|
|
22
27
|
description: str,
|
|
23
28
|
registration_model: type[BaseModel],
|
|
24
29
|
registration_handler,
|
|
25
|
-
trigger_handler,
|
|
30
|
+
trigger_handler=None,
|
|
31
|
+
*,
|
|
32
|
+
trigger_type: TriggerType = TriggerType.WEBHOOK,
|
|
33
|
+
poll_handler: Any | None = None,
|
|
34
|
+
default_crontab: str | None = None,
|
|
26
35
|
):
|
|
27
36
|
self.id = id
|
|
28
37
|
self.provider = provider
|
|
@@ -31,26 +40,44 @@ class TriggerTemplate:
|
|
|
31
40
|
self.registration_model = registration_model
|
|
32
41
|
self.registration_handler = registration_handler
|
|
33
42
|
self.trigger_handler = trigger_handler
|
|
43
|
+
self.trigger_type = trigger_type
|
|
44
|
+
self.poll_handler = poll_handler
|
|
45
|
+
self.default_crontab = default_crontab
|
|
34
46
|
|
|
35
47
|
self._validate_handler_signatures()
|
|
36
48
|
|
|
37
49
|
def _validate_handler_signatures(self):
|
|
38
50
|
"""Validate that all handler functions have the correct signatures."""
|
|
39
|
-
# Expected: async def handler(user_id: str, auth_client: Client, registration: RegistrationModel) -> TriggerRegistrationResult
|
|
51
|
+
# Expected reg: async def handler(request: Request, user_id: str, auth_client: Client, registration: RegistrationModel) -> TriggerRegistrationResult
|
|
40
52
|
self._validate_handler(
|
|
41
53
|
"registration_handler",
|
|
42
54
|
self.registration_handler,
|
|
43
|
-
[str, Client, self.registration_model],
|
|
55
|
+
[Request, str, Client, self.registration_model],
|
|
44
56
|
TriggerRegistrationResult,
|
|
45
57
|
)
|
|
46
58
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
if self.trigger_type == TriggerType.WEBHOOK:
|
|
60
|
+
if not self.trigger_handler:
|
|
61
|
+
raise TypeError(
|
|
62
|
+
f"trigger_handler required for webhook trigger '{self.id}'"
|
|
63
|
+
)
|
|
64
|
+
self._validate_handler(
|
|
65
|
+
"trigger_handler",
|
|
66
|
+
self.trigger_handler,
|
|
67
|
+
[dict[str, Any], dict[str, str], Any, Client],
|
|
68
|
+
TriggerHandlerResult,
|
|
69
|
+
)
|
|
70
|
+
else:
|
|
71
|
+
if not self.poll_handler:
|
|
72
|
+
raise TypeError(
|
|
73
|
+
f"poll_handler required for polling trigger '{self.id}'"
|
|
74
|
+
)
|
|
75
|
+
self._validate_handler(
|
|
76
|
+
"poll_handler",
|
|
77
|
+
self.poll_handler,
|
|
78
|
+
[dict[str, Any], Any, Client],
|
|
79
|
+
TriggerHandlerResult,
|
|
80
|
+
)
|
|
54
81
|
|
|
55
82
|
def _validate_handler(
|
|
56
83
|
self,
|
|
@@ -5,6 +5,7 @@ from datetime import datetime
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
from croniter import croniter
|
|
8
|
+
from fastapi import Request
|
|
8
9
|
from langchain_auth.client import Client
|
|
9
10
|
from pydantic import Field
|
|
10
11
|
|
|
@@ -12,6 +13,7 @@ from langchain_triggers.core import (
|
|
|
12
13
|
TriggerHandlerResult,
|
|
13
14
|
TriggerRegistrationModel,
|
|
14
15
|
TriggerRegistrationResult,
|
|
16
|
+
TriggerType,
|
|
15
17
|
)
|
|
16
18
|
from langchain_triggers.decorators import TriggerTemplate
|
|
17
19
|
|
|
@@ -32,7 +34,7 @@ class CronRegistration(TriggerRegistrationModel):
|
|
|
32
34
|
|
|
33
35
|
|
|
34
36
|
async def cron_registration_handler(
|
|
35
|
-
user_id: str, auth_client: Client, registration: CronRegistration
|
|
37
|
+
request: Request, user_id: str, auth_client: Client, registration: CronRegistration
|
|
36
38
|
) -> TriggerRegistrationResult:
|
|
37
39
|
"""Handle cron trigger registration - validates cron pattern and prepares for scheduling."""
|
|
38
40
|
logger.info(f"Cron registration request: {registration}")
|
|
@@ -73,20 +75,25 @@ async def cron_registration_handler(
|
|
|
73
75
|
)
|
|
74
76
|
|
|
75
77
|
|
|
76
|
-
async def
|
|
77
|
-
|
|
78
|
-
query_params: dict[str, str],
|
|
78
|
+
async def cron_poll_handler(
|
|
79
|
+
registration: dict[str, Any],
|
|
79
80
|
database,
|
|
80
81
|
auth_client: Client,
|
|
81
82
|
) -> TriggerHandlerResult:
|
|
82
|
-
"""
|
|
83
|
-
|
|
83
|
+
"""Polling handler for generic cron.
|
|
84
|
+
|
|
85
|
+
Produces a simple time-based message for linked agents.
|
|
86
|
+
"""
|
|
87
|
+
current_time = datetime.utcnow()
|
|
88
|
+
current_time_str = current_time.strftime("%A, %B %d, %Y at %H:%M UTC")
|
|
89
|
+
message = (
|
|
90
|
+
"ACTION: triggering cron from langchain-trigger-server\n"
|
|
91
|
+
f"CURRENT TIME: {current_time_str}"
|
|
92
|
+
)
|
|
84
93
|
return TriggerHandlerResult(
|
|
85
|
-
invoke_agent=
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
"message": "Cron triggers are executed by scheduler, not HTTP requests",
|
|
89
|
-
},
|
|
94
|
+
invoke_agent=True,
|
|
95
|
+
agent_messages=[message],
|
|
96
|
+
registration=registration,
|
|
90
97
|
)
|
|
91
98
|
|
|
92
99
|
|
|
@@ -97,5 +104,6 @@ cron_trigger = TriggerTemplate(
|
|
|
97
104
|
description="Triggers agents on a cron schedule",
|
|
98
105
|
registration_model=CronRegistration,
|
|
99
106
|
registration_handler=cron_registration_handler,
|
|
100
|
-
|
|
107
|
+
trigger_type=TriggerType.POLLING,
|
|
108
|
+
poll_handler=cron_poll_handler,
|
|
101
109
|
)
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
langchain_triggers/__init__.py,sha256=WoW9LC_FJRs42mLWq2iuM-jjPow2Rue50q2zm56Oul0,536
|
|
2
|
-
langchain_triggers/app.py,sha256=rVl_Yc2n0bz37mPb3E1MnPG2bCXH-bZkuuleaQIII8U,38569
|
|
3
|
-
langchain_triggers/core.py,sha256=_CNZRyj78yCHG8FACwhCmZ0zvRoWls924OIFYMOC27Q,3772
|
|
4
|
-
langchain_triggers/cron_manager.py,sha256=ISo-P2gw7eQ6y7xWQOfojqcJr_K-zagZt9Ocy8nX0fw,10477
|
|
5
|
-
langchain_triggers/decorators.py,sha256=zsfgf171qkEDdIiSn8LUr--3dz6bxBBKZO6dpRM2ILs,3711
|
|
6
|
-
langchain_triggers/auth/__init__.py,sha256=RtDKuBoKYuyHzLNpKr74cmALO0PhHlWO9Ho7k3CUYFE,349
|
|
7
|
-
langchain_triggers/auth/slack_hmac.py,sha256=kiwjhTXITgQvLAtEcOv8BnnWJRJcxaQ9dXkQm3JJDQ4,2948
|
|
8
|
-
langchain_triggers/database/__init__.py,sha256=B1I1qmVr3U1CSf0VkjxsL4W5QGda5T7uB_CsJq6yBF4,535
|
|
9
|
-
langchain_triggers/database/interface.py,sha256=jpADOOwcBQo1ZichgiZVaOvfZqEqVVo8Ea7ATWWTSBE,4283
|
|
10
|
-
langchain_triggers/database/supabase.py,sha256=zi_75GbqRvzzlXd5EgfYof4h6vHWOLS4I1759wvY9kQ,17009
|
|
11
|
-
langchain_triggers/triggers/__init__.py,sha256=Uw1544gxzN4XDRn2RzpZ5EAG6EAF38ZYQtVvlciEsMs,146
|
|
12
|
-
langchain_triggers/triggers/cron_trigger.py,sha256=SeWz_ETBCBO1_r96tzTZgsvn4BdF4yMKabygjmHoGwY,3174
|
|
13
|
-
langchain_trigger_server-0.3.dist-info/METADATA,sha256=eC_TiB7Y3vPvK4pQmx2WFteIHruiI_u_zWDjG9Uqpjc,1484
|
|
14
|
-
langchain_trigger_server-0.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
15
|
-
langchain_trigger_server-0.3.dist-info/RECORD,,
|
|
File without changes
|