langchain-trigger-server 0.3.6__tar.gz → 0.3.8__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.
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/PKG-INFO +1 -1
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/langchain_triggers/__init__.py +0 -2
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/langchain_triggers/app.py +6 -281
- langchain_trigger_server-0.3.8/langchain_triggers/auth/__init__.py +3 -0
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/langchain_triggers/core.py +0 -27
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/langchain_triggers/cron_manager.py +5 -83
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/langchain_triggers/decorators.py +7 -8
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/langchain_triggers/triggers/cron_trigger.py +71 -15
- langchain_trigger_server-0.3.8/langchain_triggers/util.py +73 -0
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/pyproject.toml +1 -1
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/tests/unit/test_cron_manager_polling_filter.py +3 -5
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/tests/unit/test_trigger_server_api.py +3 -3
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/uv.lock +1 -1
- langchain_trigger_server-0.3.8/version_comparison.txt +1 -0
- langchain_trigger_server-0.3.6/langchain_triggers/auth/__init__.py +0 -15
- langchain_trigger_server-0.3.6/langchain_triggers/auth/slack_hmac.py +0 -90
- langchain_trigger_server-0.3.6/version_comparison.txt +0 -1
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/.github/actions/uv_setup/action.yml +0 -0
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/.github/workflows/_lint.yml +0 -0
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/.github/workflows/_test.yml +0 -0
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/.github/workflows/ci.yml +0 -0
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/.github/workflows/release.yml +0 -0
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/.gitignore +0 -0
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/Makefile +0 -0
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/README.md +0 -0
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/langchain_triggers/database/__init__.py +0 -0
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/langchain_triggers/database/interface.py +0 -0
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/langchain_triggers/triggers/__init__.py +0 -0
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/tests/__init__.py +0 -0
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/tests/unit/__init__.py +0 -0
- {langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/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.
|
|
3
|
+
Version: 0.3.8
|
|
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
|
{langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/langchain_triggers/__init__.py
RENAMED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from .app import TriggerServer
|
|
4
4
|
from .core import (
|
|
5
|
-
TriggerHandlerResult,
|
|
6
5
|
TriggerRegistrationModel,
|
|
7
6
|
TriggerRegistrationResult,
|
|
8
7
|
UserAuthInfo,
|
|
@@ -15,7 +14,6 @@ __version__ = "0.1.0"
|
|
|
15
14
|
__all__ = [
|
|
16
15
|
"UserAuthInfo",
|
|
17
16
|
"TriggerRegistrationModel",
|
|
18
|
-
"TriggerHandlerResult",
|
|
19
17
|
"TriggerRegistrationResult",
|
|
20
18
|
"TriggerTemplate",
|
|
21
19
|
"TriggerServer",
|
|
@@ -15,12 +15,6 @@ from langgraph_sdk import get_client
|
|
|
15
15
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
16
16
|
from starlette.responses import Response
|
|
17
17
|
|
|
18
|
-
from .auth.slack_hmac import (
|
|
19
|
-
SlackSignatureVerificationError,
|
|
20
|
-
extract_slack_headers,
|
|
21
|
-
get_slack_signing_secret,
|
|
22
|
-
verify_slack_signature,
|
|
23
|
-
)
|
|
24
18
|
from .core import TriggerType
|
|
25
19
|
from .cron_manager import CronTriggerManager
|
|
26
20
|
from .database import TriggerDatabaseInterface
|
|
@@ -361,7 +355,7 @@ class TriggerServer:
|
|
|
361
355
|
detail=f"You already have a registration with this configuration for trigger type '{trigger.id}'. Registration ID: {existing_registration.get('id')}",
|
|
362
356
|
)
|
|
363
357
|
result = await trigger.registration_handler(
|
|
364
|
-
request, user_id,
|
|
358
|
+
request, user_id, registration_instance
|
|
365
359
|
)
|
|
366
360
|
|
|
367
361
|
# Check if handler requested to skip registration (e.g., for OAuth or URL verification)
|
|
@@ -643,143 +637,18 @@ class TriggerServer:
|
|
|
643
637
|
getattr(trigger, "provider", "<unknown>"),
|
|
644
638
|
request.headers.get("content-type", ""),
|
|
645
639
|
)
|
|
646
|
-
if request.method == "POST":
|
|
647
|
-
if request.headers.get("content-type", "").startswith(
|
|
648
|
-
"application/json"
|
|
649
|
-
):
|
|
650
|
-
# Read body once for both auth and parsing
|
|
651
|
-
body_bytes = await request.body()
|
|
652
|
-
body_str = body_bytes.decode("utf-8")
|
|
653
|
-
logger.debug(
|
|
654
|
-
"request_body_read bytes=%s",
|
|
655
|
-
len(body_bytes) if body_bytes is not None else 0,
|
|
656
|
-
)
|
|
657
|
-
|
|
658
|
-
# TODO(sam/palash): We should not have API specific things in this framework repo. We should clean this up.
|
|
659
|
-
if self._is_slack_trigger(trigger):
|
|
660
|
-
logger.info(
|
|
661
|
-
"slack_request_detected trigger_id=%s verifying_signature=true",
|
|
662
|
-
getattr(trigger, "id", "<unknown>"),
|
|
663
|
-
)
|
|
664
|
-
await self._verify_slack_webhook_auth_with_body(
|
|
665
|
-
request, body_str
|
|
666
|
-
)
|
|
667
640
|
|
|
668
|
-
import json
|
|
669
|
-
|
|
670
|
-
try:
|
|
671
|
-
payload = json.loads(body_str)
|
|
672
|
-
except Exception as e:
|
|
673
|
-
logger.error("json_parse_error error=%s", e)
|
|
674
|
-
raise
|
|
675
|
-
logger.info(
|
|
676
|
-
"payload_parsed kind=json keys=%s",
|
|
677
|
-
sorted(payload.keys())
|
|
678
|
-
if isinstance(payload, dict)
|
|
679
|
-
else "<non-dict>",
|
|
680
|
-
)
|
|
681
|
-
|
|
682
|
-
if (
|
|
683
|
-
payload.get("type") == "url_verification"
|
|
684
|
-
and "challenge" in payload
|
|
685
|
-
):
|
|
686
|
-
logger.info("Responding to Slack URL verification challenge")
|
|
687
|
-
return {"challenge": payload["challenge"]}
|
|
688
|
-
else:
|
|
689
|
-
# Handle form data or other content types
|
|
690
|
-
body = await request.body()
|
|
691
|
-
payload = {"raw_body": body.decode("utf-8") if body else ""}
|
|
692
|
-
logger.info(
|
|
693
|
-
"payload_parsed kind=raw length=%s",
|
|
694
|
-
len(payload.get("raw_body", "")),
|
|
695
|
-
)
|
|
696
|
-
else:
|
|
697
|
-
payload = dict(request.query_params)
|
|
698
|
-
logger.info(
|
|
699
|
-
"payload_parsed kind=query keys=%s",
|
|
700
|
-
sorted(payload.keys()),
|
|
701
|
-
)
|
|
702
|
-
|
|
703
|
-
query_params = dict(request.query_params)
|
|
704
641
|
logger.info(
|
|
705
|
-
"invoking_trigger_handler trigger_id=%s provider=%s
|
|
642
|
+
"invoking_trigger_handler trigger_id=%s provider=%s",
|
|
706
643
|
getattr(trigger, "id", "<unknown>"),
|
|
707
644
|
getattr(trigger, "provider", "<unknown>"),
|
|
708
|
-
sorted(query_params.keys()),
|
|
709
|
-
)
|
|
710
|
-
result = await trigger.trigger_handler(
|
|
711
|
-
payload, query_params, self.database, self.langchain_auth_client
|
|
712
|
-
)
|
|
713
|
-
logger.info(
|
|
714
|
-
"trigger_handler_completed invoke_agent=%s messages=%s has_registration=%s",
|
|
715
|
-
getattr(result, "invoke_agent", None),
|
|
716
|
-
len(getattr(result, "agent_messages", []) or []),
|
|
717
|
-
bool(getattr(result, "registration", None)),
|
|
718
|
-
)
|
|
719
|
-
if not result.invoke_agent:
|
|
720
|
-
logger.info(
|
|
721
|
-
"no_agent_invocation trigger_id=%s returning_handler_response=true",
|
|
722
|
-
getattr(trigger, "id", "<unknown>"),
|
|
723
|
-
)
|
|
724
|
-
return result.response_body
|
|
725
|
-
|
|
726
|
-
registration_id = result.registration["id"]
|
|
727
|
-
agent_links = await self.database.get_agents_for_trigger(registration_id)
|
|
728
|
-
logger.info(
|
|
729
|
-
"agent_links_fetched registration_id=%s count=%s",
|
|
730
|
-
registration_id,
|
|
731
|
-
len(agent_links) if agent_links is not None else 0,
|
|
732
645
|
)
|
|
733
|
-
|
|
734
|
-
agents_invoked = 0
|
|
735
|
-
# Iterate through each message and invoke agents for each
|
|
736
|
-
for message in result.agent_messages:
|
|
737
|
-
logger.debug(
|
|
738
|
-
"processing_agent_message message_len=%s",
|
|
739
|
-
len(str(message)) if message is not None else 0,
|
|
740
|
-
)
|
|
741
|
-
for agent_link in agent_links:
|
|
742
|
-
agent_id = (
|
|
743
|
-
agent_link
|
|
744
|
-
if isinstance(agent_link, str)
|
|
745
|
-
else agent_link.get("agent_id")
|
|
746
|
-
)
|
|
747
|
-
# Ensure agent_id and user_id are strings for JSON serialization
|
|
748
|
-
agent_id_str = str(agent_id)
|
|
749
|
-
user_id_str = str(result.registration["user_id"])
|
|
750
|
-
tenant_id_str = str(result.registration["tenant_id"])
|
|
751
|
-
|
|
752
|
-
agent_input = {"messages": [{"role": "human", "content": message}]}
|
|
753
|
-
|
|
754
|
-
try:
|
|
755
|
-
logger.info(
|
|
756
|
-
"invoking_agent assistant_id=%s user_id=%s tenant_id=%s",
|
|
757
|
-
agent_id_str,
|
|
758
|
-
user_id_str,
|
|
759
|
-
tenant_id_str,
|
|
760
|
-
)
|
|
761
|
-
success = await self._invoke_agent(
|
|
762
|
-
agent_id=agent_id_str,
|
|
763
|
-
user_id=user_id_str,
|
|
764
|
-
tenant_id=tenant_id_str,
|
|
765
|
-
input_data=agent_input,
|
|
766
|
-
)
|
|
767
|
-
if success:
|
|
768
|
-
agents_invoked += 1
|
|
769
|
-
logger.info(
|
|
770
|
-
"agent_invocation_success assistant_id=%s registration_id=%s",
|
|
771
|
-
agent_id_str,
|
|
772
|
-
registration_id,
|
|
773
|
-
)
|
|
774
|
-
except Exception as e:
|
|
775
|
-
logger.error(
|
|
776
|
-
f"Error invoking agent {agent_id_str}: {e}", exc_info=True
|
|
777
|
-
)
|
|
646
|
+
response = await trigger.trigger_handler(request, self.database)
|
|
778
647
|
logger.info(
|
|
779
|
-
|
|
648
|
+
"trigger_handler_completed trigger_id=%s",
|
|
649
|
+
getattr(trigger, "id", "<unknown>"),
|
|
780
650
|
)
|
|
781
|
-
|
|
782
|
-
return {"success": True, "agents_invoked": agents_invoked}
|
|
651
|
+
return response
|
|
783
652
|
|
|
784
653
|
except HTTPException:
|
|
785
654
|
raise
|
|
@@ -856,150 +725,6 @@ class TriggerServer:
|
|
|
856
725
|
logger.error(f"Error invoking agent {agent_id}: {e}")
|
|
857
726
|
raise
|
|
858
727
|
|
|
859
|
-
def _is_slack_trigger(self, trigger: TriggerTemplate) -> bool:
|
|
860
|
-
"""Check if a trigger is from Slack and requires HMAC signature verification."""
|
|
861
|
-
return trigger.provider.lower() == "slack" or "slack" in trigger.id.lower()
|
|
862
|
-
|
|
863
|
-
async def _verify_slack_webhook_auth(self, request: Request) -> None:
|
|
864
|
-
"""Verify Slack HMAC signature for webhook requests.
|
|
865
|
-
|
|
866
|
-
Slack uses HMAC-SHA256 signatures to verify webhook authenticity.
|
|
867
|
-
The signature is computed from the timestamp, body, and signing secret.
|
|
868
|
-
|
|
869
|
-
Args:
|
|
870
|
-
request: The FastAPI request object
|
|
871
|
-
|
|
872
|
-
Raises:
|
|
873
|
-
HTTPException: If authentication fails
|
|
874
|
-
"""
|
|
875
|
-
try:
|
|
876
|
-
signing_secret = get_slack_signing_secret()
|
|
877
|
-
if not signing_secret:
|
|
878
|
-
logger.error("SLACK_SIGNING_SECRET environment variable not set")
|
|
879
|
-
raise HTTPException(
|
|
880
|
-
status_code=500,
|
|
881
|
-
detail="Slack signing secret not configured on server",
|
|
882
|
-
)
|
|
883
|
-
|
|
884
|
-
headers_dict = dict(request.headers)
|
|
885
|
-
signature, timestamp = extract_slack_headers(headers_dict)
|
|
886
|
-
|
|
887
|
-
if not signature:
|
|
888
|
-
logger.error("Missing X-Slack-Signature header")
|
|
889
|
-
raise HTTPException(
|
|
890
|
-
status_code=401,
|
|
891
|
-
detail="Missing X-Slack-Signature header. Slack webhooks require signature verification.",
|
|
892
|
-
)
|
|
893
|
-
|
|
894
|
-
if not timestamp:
|
|
895
|
-
logger.error("Missing X-Slack-Request-Timestamp header")
|
|
896
|
-
raise HTTPException(
|
|
897
|
-
status_code=401,
|
|
898
|
-
detail="Missing X-Slack-Request-Timestamp header. Slack webhooks require timestamp.",
|
|
899
|
-
)
|
|
900
|
-
|
|
901
|
-
body = await request.body()
|
|
902
|
-
body_str = body.decode("utf-8")
|
|
903
|
-
|
|
904
|
-
try:
|
|
905
|
-
verify_slack_signature(
|
|
906
|
-
signing_secret=signing_secret,
|
|
907
|
-
timestamp=timestamp,
|
|
908
|
-
body=body_str,
|
|
909
|
-
signature=signature,
|
|
910
|
-
)
|
|
911
|
-
logger.info(
|
|
912
|
-
f"Successfully verified Slack webhook signature. Timestamp: {timestamp}"
|
|
913
|
-
)
|
|
914
|
-
except SlackSignatureVerificationError as e:
|
|
915
|
-
logger.error(f"Slack signature verification failed: {e}")
|
|
916
|
-
raise HTTPException(
|
|
917
|
-
status_code=401,
|
|
918
|
-
detail=f"Slack signature verification failed: {str(e)}",
|
|
919
|
-
)
|
|
920
|
-
|
|
921
|
-
# Store verification info in request state
|
|
922
|
-
request.state.slack_verified = True
|
|
923
|
-
request.state.slack_timestamp = timestamp
|
|
924
|
-
|
|
925
|
-
except HTTPException:
|
|
926
|
-
raise
|
|
927
|
-
except Exception as e:
|
|
928
|
-
logger.error(f"Unexpected error during Slack webhook authentication: {e}")
|
|
929
|
-
raise HTTPException(
|
|
930
|
-
status_code=500, detail=f"Authentication error: {str(e)}"
|
|
931
|
-
)
|
|
932
|
-
|
|
933
|
-
async def _verify_slack_webhook_auth_with_body(
|
|
934
|
-
self, request: Request, body_str: str
|
|
935
|
-
) -> None:
|
|
936
|
-
"""Verify Slack HMAC signature for webhook requests using pre-read body.
|
|
937
|
-
|
|
938
|
-
Slack uses HMAC-SHA256 signatures to verify webhook authenticity.
|
|
939
|
-
The signature is computed from the timestamp, body, and signing secret.
|
|
940
|
-
|
|
941
|
-
Args:
|
|
942
|
-
request: The FastAPI request object
|
|
943
|
-
body_str: The request body as a string (already read)
|
|
944
|
-
|
|
945
|
-
Raises:
|
|
946
|
-
HTTPException: If authentication fails
|
|
947
|
-
"""
|
|
948
|
-
try:
|
|
949
|
-
signing_secret = get_slack_signing_secret()
|
|
950
|
-
if not signing_secret:
|
|
951
|
-
logger.error("SLACK_SIGNING_SECRET environment variable not set")
|
|
952
|
-
raise HTTPException(
|
|
953
|
-
status_code=500,
|
|
954
|
-
detail="Slack signing secret not configured on server",
|
|
955
|
-
)
|
|
956
|
-
|
|
957
|
-
headers_dict = dict(request.headers)
|
|
958
|
-
signature, timestamp = extract_slack_headers(headers_dict)
|
|
959
|
-
|
|
960
|
-
if not signature:
|
|
961
|
-
logger.error("Missing X-Slack-Signature header")
|
|
962
|
-
raise HTTPException(
|
|
963
|
-
status_code=401,
|
|
964
|
-
detail="Missing X-Slack-Signature header. Slack webhooks require signature verification.",
|
|
965
|
-
)
|
|
966
|
-
|
|
967
|
-
if not timestamp:
|
|
968
|
-
logger.error("Missing X-Slack-Request-Timestamp header")
|
|
969
|
-
raise HTTPException(
|
|
970
|
-
status_code=401,
|
|
971
|
-
detail="Missing X-Slack-Request-Timestamp header. Slack webhooks require timestamp.",
|
|
972
|
-
)
|
|
973
|
-
|
|
974
|
-
try:
|
|
975
|
-
verify_slack_signature(
|
|
976
|
-
signing_secret=signing_secret,
|
|
977
|
-
timestamp=timestamp,
|
|
978
|
-
body=body_str,
|
|
979
|
-
signature=signature,
|
|
980
|
-
)
|
|
981
|
-
logger.info(
|
|
982
|
-
f"Successfully verified Slack webhook signature. Timestamp: {timestamp}"
|
|
983
|
-
)
|
|
984
|
-
except SlackSignatureVerificationError as e:
|
|
985
|
-
logger.error(f"Slack signature verification failed: {e}")
|
|
986
|
-
raise HTTPException(
|
|
987
|
-
status_code=401,
|
|
988
|
-
detail=f"Slack signature verification failed: {str(e)}",
|
|
989
|
-
)
|
|
990
|
-
|
|
991
|
-
# Store verification info in request state
|
|
992
|
-
request.state.slack_verified = True
|
|
993
|
-
request.state.slack_timestamp = timestamp
|
|
994
|
-
|
|
995
|
-
except HTTPException:
|
|
996
|
-
raise
|
|
997
|
-
except Exception as e:
|
|
998
|
-
logger.error(f"Unexpected error during Slack webhook authentication: {e}")
|
|
999
|
-
raise HTTPException(
|
|
1000
|
-
status_code=500, detail=f"Authentication error: {str(e)}"
|
|
1001
|
-
)
|
|
1002
|
-
|
|
1003
728
|
def get_app(self) -> FastAPI:
|
|
1004
729
|
"""Get the FastAPI app instance."""
|
|
1005
730
|
return self.app
|
{langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/langchain_triggers/core.py
RENAMED
|
@@ -47,33 +47,6 @@ class AgentInvocationRequest(BaseModel):
|
|
|
47
47
|
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
class TriggerHandlerResult(BaseModel):
|
|
51
|
-
"""Result returned by trigger handlers."""
|
|
52
|
-
|
|
53
|
-
invoke_agent: bool = Field(
|
|
54
|
-
default=True, description="Whether to invoke agents for this event"
|
|
55
|
-
)
|
|
56
|
-
agent_messages: list[str] | None = Field(
|
|
57
|
-
default=None,
|
|
58
|
-
description="List of messages to send to agents (one invocation per message)",
|
|
59
|
-
)
|
|
60
|
-
response_body: dict[str, Any] | None = Field(
|
|
61
|
-
default=None, description="Custom HTTP response body (when invoke_agent=False)"
|
|
62
|
-
)
|
|
63
|
-
registration: dict[str, Any] | None = Field(
|
|
64
|
-
default=None, description="Registration data (required when invoke_agent=True)"
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
def model_post_init(self, __context) -> None:
|
|
68
|
-
"""Validate that required fields are provided based on invoke_agent."""
|
|
69
|
-
if self.invoke_agent and not self.agent_messages:
|
|
70
|
-
raise ValueError("agent_messages is required when invoke_agent=True")
|
|
71
|
-
if self.invoke_agent and not self.registration:
|
|
72
|
-
raise ValueError("registration is required when invoke_agent=True")
|
|
73
|
-
if not self.invoke_agent and not self.response_body:
|
|
74
|
-
raise ValueError("response_body is required when invoke_agent=False")
|
|
75
|
-
|
|
76
|
-
|
|
77
50
|
class TriggerRegistrationResult(BaseModel):
|
|
78
51
|
"""Result returned by registration handlers."""
|
|
79
52
|
|
{langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/langchain_triggers/cron_manager.py
RENAMED
|
@@ -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.core import
|
|
11
|
+
from langchain_triggers.core import TriggerType
|
|
12
12
|
|
|
13
13
|
logger = logging.getLogger(__name__)
|
|
14
14
|
|
|
@@ -280,21 +280,10 @@ class CronTriggerManager:
|
|
|
280
280
|
await self._record_execution(execution)
|
|
281
281
|
|
|
282
282
|
async def execute_cron_job(self, registration: dict[str, Any]) -> int:
|
|
283
|
-
"""Execute a cron job -
|
|
283
|
+
"""Execute a cron job - calls poll handler which invokes agents."""
|
|
284
284
|
registration_id = registration["id"]
|
|
285
|
-
user_id = registration["user_id"]
|
|
286
285
|
template_id = registration.get("template_id")
|
|
287
286
|
template_id = str(template_id) if template_id is not None else None
|
|
288
|
-
tenant_id = str(registration.get("tenant_id"))
|
|
289
|
-
|
|
290
|
-
# Get agent links
|
|
291
|
-
agent_links = await self.trigger_server.database.get_agents_for_trigger(
|
|
292
|
-
registration_id
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
if not agent_links:
|
|
296
|
-
logger.warning(f"No agents linked to cron job {registration_id}")
|
|
297
|
-
return 0
|
|
298
287
|
|
|
299
288
|
tmpl = next(
|
|
300
289
|
(t for t in self.trigger_server.triggers if t.id == template_id), None
|
|
@@ -308,85 +297,18 @@ class CronTriggerManager:
|
|
|
308
297
|
)
|
|
309
298
|
return 0
|
|
310
299
|
|
|
311
|
-
|
|
300
|
+
response = await tmpl.poll_handler(
|
|
312
301
|
registration,
|
|
313
302
|
self.trigger_server.database,
|
|
314
|
-
self.trigger_server.langchain_auth_client,
|
|
315
303
|
)
|
|
316
304
|
|
|
317
|
-
|
|
318
|
-
logger.info(
|
|
319
|
-
"poll_result "
|
|
320
|
-
f"registration_id={registration_id} "
|
|
321
|
-
f"trigger_id={template_id} "
|
|
322
|
-
f"provider={(tmpl.provider or '').lower()} "
|
|
323
|
-
f"invoke_agent=false messages_count=0"
|
|
324
|
-
)
|
|
325
|
-
return 0
|
|
326
|
-
|
|
327
|
-
agents_invoked = 0
|
|
328
|
-
messages = result.agent_messages or []
|
|
329
|
-
|
|
305
|
+
agents_invoked = response.get("agents_invoked", 0)
|
|
330
306
|
logger.info(
|
|
331
307
|
"poll_result "
|
|
332
308
|
f"registration_id={registration_id} "
|
|
333
309
|
f"trigger_id={template_id} "
|
|
334
310
|
f"provider={(tmpl.provider or '').lower()} "
|
|
335
|
-
f"
|
|
336
|
-
f"agents_linked={len(agent_links)}"
|
|
337
|
-
)
|
|
338
|
-
|
|
339
|
-
for _message in messages:
|
|
340
|
-
for agent_link in agent_links:
|
|
341
|
-
agent_id = (
|
|
342
|
-
agent_link
|
|
343
|
-
if isinstance(agent_link, str)
|
|
344
|
-
else agent_link.get("agent_id")
|
|
345
|
-
)
|
|
346
|
-
# Ensure agent_id and user_id are strings for JSON serialization
|
|
347
|
-
agent_id_str = str(agent_id)
|
|
348
|
-
user_id_str = str(user_id)
|
|
349
|
-
tenant_id_str = str(tenant_id)
|
|
350
|
-
|
|
351
|
-
current_time = datetime.utcnow()
|
|
352
|
-
current_time_str = current_time.strftime("%A, %B %d, %Y at %H:%M UTC")
|
|
353
|
-
|
|
354
|
-
agent_input = {
|
|
355
|
-
"messages": [
|
|
356
|
-
{
|
|
357
|
-
"role": "human",
|
|
358
|
-
"content": f"Cron trigger fired at {current_time_str}",
|
|
359
|
-
}
|
|
360
|
-
]
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
try:
|
|
364
|
-
success = await self.trigger_server._invoke_agent(
|
|
365
|
-
agent_id=agent_id_str,
|
|
366
|
-
user_id=user_id_str,
|
|
367
|
-
tenant_id=tenant_id_str,
|
|
368
|
-
input_data=agent_input,
|
|
369
|
-
)
|
|
370
|
-
if success:
|
|
371
|
-
logger.info(
|
|
372
|
-
"invoke_agent_ok "
|
|
373
|
-
f"registration_id={registration_id} "
|
|
374
|
-
f"agent_id={agent_id_str}"
|
|
375
|
-
)
|
|
376
|
-
agents_invoked += 1
|
|
377
|
-
except Exception as e:
|
|
378
|
-
logger.error(
|
|
379
|
-
"invoke_agent_err "
|
|
380
|
-
f"registration_id={registration_id} "
|
|
381
|
-
f"agent_id={agent_id_str} "
|
|
382
|
-
f"error={str(e)}"
|
|
383
|
-
)
|
|
384
|
-
|
|
385
|
-
logger.info(
|
|
386
|
-
"poll_invoke_summary "
|
|
387
|
-
f"registration_id={registration_id} "
|
|
388
|
-
f"agents_invoked={agents_invoked} "
|
|
389
|
-
f"messages_count={len(messages)}"
|
|
311
|
+
f"agents_invoked={agents_invoked}"
|
|
390
312
|
)
|
|
391
313
|
return agents_invoked
|
|
392
314
|
|
{langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/langchain_triggers/decorators.py
RENAMED
|
@@ -10,10 +10,9 @@ import inspect
|
|
|
10
10
|
from typing import Any, get_type_hints
|
|
11
11
|
|
|
12
12
|
from fastapi import Request
|
|
13
|
-
from langchain_auth.client import Client
|
|
14
13
|
from pydantic import BaseModel
|
|
15
14
|
|
|
16
|
-
from .core import
|
|
15
|
+
from .core import TriggerRegistrationResult, TriggerType
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
class TriggerTemplate:
|
|
@@ -48,11 +47,11 @@ class TriggerTemplate:
|
|
|
48
47
|
|
|
49
48
|
def _validate_handler_signatures(self):
|
|
50
49
|
"""Validate that all handler functions have the correct signatures."""
|
|
51
|
-
# Expected reg: async def handler(request: Request, user_id: str,
|
|
50
|
+
# Expected reg: async def handler(request: Request, user_id: str, registration: RegistrationModel) -> TriggerRegistrationResult
|
|
52
51
|
self._validate_handler(
|
|
53
52
|
"registration_handler",
|
|
54
53
|
self.registration_handler,
|
|
55
|
-
[Request, str,
|
|
54
|
+
[Request, str, self.registration_model],
|
|
56
55
|
TriggerRegistrationResult,
|
|
57
56
|
)
|
|
58
57
|
|
|
@@ -64,8 +63,8 @@ class TriggerTemplate:
|
|
|
64
63
|
self._validate_handler(
|
|
65
64
|
"trigger_handler",
|
|
66
65
|
self.trigger_handler,
|
|
67
|
-
[
|
|
68
|
-
|
|
66
|
+
[Request, Any],
|
|
67
|
+
dict[str, Any],
|
|
69
68
|
)
|
|
70
69
|
else:
|
|
71
70
|
if not self.poll_handler:
|
|
@@ -75,8 +74,8 @@ class TriggerTemplate:
|
|
|
75
74
|
self._validate_handler(
|
|
76
75
|
"poll_handler",
|
|
77
76
|
self.poll_handler,
|
|
78
|
-
[dict[str, Any], Any
|
|
79
|
-
|
|
77
|
+
[dict[str, Any], Any],
|
|
78
|
+
dict[str, Any],
|
|
80
79
|
)
|
|
81
80
|
|
|
82
81
|
def _validate_handler(
|
|
@@ -1,21 +1,22 @@
|
|
|
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
|
|
|
7
8
|
from croniter import croniter
|
|
8
9
|
from fastapi import Request
|
|
9
|
-
from
|
|
10
|
+
from langgraph_sdk import get_client
|
|
10
11
|
from pydantic import Field
|
|
11
12
|
|
|
12
13
|
from langchain_triggers.core import (
|
|
13
|
-
TriggerHandlerResult,
|
|
14
14
|
TriggerRegistrationModel,
|
|
15
15
|
TriggerRegistrationResult,
|
|
16
16
|
TriggerType,
|
|
17
17
|
)
|
|
18
18
|
from langchain_triggers.decorators import TriggerTemplate
|
|
19
|
+
from langchain_triggers.util import create_service_auth_headers, get_langgraph_url
|
|
19
20
|
|
|
20
21
|
logger = logging.getLogger(__name__)
|
|
21
22
|
|
|
@@ -34,7 +35,7 @@ class CronRegistration(TriggerRegistrationModel):
|
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
async def cron_registration_handler(
|
|
37
|
-
request: Request, user_id: str,
|
|
38
|
+
request: Request, user_id: str, registration: CronRegistration
|
|
38
39
|
) -> TriggerRegistrationResult:
|
|
39
40
|
"""Handle cron trigger registration - validates cron pattern and prepares for scheduling."""
|
|
40
41
|
logger.info(f"Cron registration request: {registration}")
|
|
@@ -78,18 +79,73 @@ async def cron_registration_handler(
|
|
|
78
79
|
async def cron_poll_handler(
|
|
79
80
|
registration: dict[str, Any],
|
|
80
81
|
database,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
""
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
82
|
+
) -> dict[str, Any]:
|
|
83
|
+
"""Polling handler for generic cron - invokes agents directly."""
|
|
84
|
+
registration_id = registration["id"]
|
|
85
|
+
user_id = str(registration["user_id"])
|
|
86
|
+
tenant_id = str(registration.get("tenant_id", ""))
|
|
87
|
+
|
|
88
|
+
agent_links = await database.get_agents_for_trigger(registration_id)
|
|
89
|
+
|
|
90
|
+
if not agent_links:
|
|
91
|
+
logger.info(f"cron_no_linked_agents registration_id={registration_id}")
|
|
92
|
+
return {"success": True, "message": "No linked agents", "agents_invoked": 0}
|
|
93
|
+
|
|
94
|
+
langgraph_url = get_langgraph_url()
|
|
95
|
+
|
|
96
|
+
client = get_client(url=langgraph_url, api_key=None)
|
|
97
|
+
headers = create_service_auth_headers(user_id, tenant_id)
|
|
98
|
+
|
|
99
|
+
current_time = datetime.utcnow()
|
|
100
|
+
current_time_str = current_time.strftime("%A, %B %d, %Y at %H:%M UTC")
|
|
101
|
+
|
|
102
|
+
agents_invoked = 0
|
|
103
|
+
for agent_link in agent_links:
|
|
104
|
+
agent_id = str(
|
|
105
|
+
agent_link if isinstance(agent_link, str) else agent_link.get("agent_id")
|
|
106
|
+
)
|
|
107
|
+
|
|
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(f"cron_thread_create_failed thread_id={thread_id} error={str(thread_err)}")
|
|
125
|
+
|
|
126
|
+
await client.runs.create(
|
|
127
|
+
thread_id,
|
|
128
|
+
agent_id,
|
|
129
|
+
input={
|
|
130
|
+
"messages": [
|
|
131
|
+
{
|
|
132
|
+
"role": "human",
|
|
133
|
+
"content": f"Cron trigger fired at {current_time_str}",
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
},
|
|
137
|
+
headers=headers,
|
|
138
|
+
)
|
|
139
|
+
logger.info(
|
|
140
|
+
f"cron_run_ok registration_id={registration_id} agent_id={agent_id} thread_id={thread_id}"
|
|
141
|
+
)
|
|
142
|
+
agents_invoked += 1
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.error(
|
|
145
|
+
f"cron_run_err registration_id={registration_id} agent_id={agent_id} error={str(e)}"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return {"success": True, "agents_invoked": agents_invoked}
|
|
93
149
|
|
|
94
150
|
|
|
95
151
|
cron_trigger = TriggerTemplate(
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Utility functions for trigger handlers."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from datetime import UTC, datetime, timedelta
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import jwt
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_x_service_jwt_token(
|
|
11
|
+
payload: dict[str, Any] | None = None, expiration_seconds: int = 60 * 60
|
|
12
|
+
) -> str:
|
|
13
|
+
"""Create X-Service-Key JWT token for service-to-service authentication.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
payload: Optional payload to include in JWT
|
|
17
|
+
expiration_seconds: Token expiration time in seconds (default 1 hour)
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
JWT token string
|
|
21
|
+
"""
|
|
22
|
+
exp_datetime = datetime.now(tz=UTC) + timedelta(seconds=expiration_seconds)
|
|
23
|
+
exp = int(exp_datetime.timestamp())
|
|
24
|
+
|
|
25
|
+
payload = payload or {}
|
|
26
|
+
payload = {
|
|
27
|
+
"sub": "unspecified",
|
|
28
|
+
"exp": exp,
|
|
29
|
+
**payload,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
secret = os.environ["X_SERVICE_AUTH_JWT_SECRET"]
|
|
33
|
+
|
|
34
|
+
return jwt.encode(
|
|
35
|
+
payload,
|
|
36
|
+
secret,
|
|
37
|
+
algorithm="HS256",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def create_service_auth_headers(user_id: str, tenant_id: str) -> dict[str, str]:
|
|
42
|
+
"""Create authentication headers with X-Service-Key JWT token.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
user_id: User ID for the request
|
|
46
|
+
tenant_id: Tenant ID for the request
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Dictionary of authentication headers
|
|
50
|
+
"""
|
|
51
|
+
headers = {
|
|
52
|
+
"x-api-key": "",
|
|
53
|
+
"x-auth-scheme": "langsmith-agent",
|
|
54
|
+
"x-user-id": user_id,
|
|
55
|
+
"x-tenant-id": tenant_id,
|
|
56
|
+
"x-service-key": get_x_service_jwt_token(
|
|
57
|
+
payload={
|
|
58
|
+
"tenant_id": tenant_id,
|
|
59
|
+
"user_id": user_id,
|
|
60
|
+
}
|
|
61
|
+
),
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return headers
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_langgraph_url() -> str:
|
|
68
|
+
"""Get LangGraph API URL from environment.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
LangGraph API URL
|
|
72
|
+
"""
|
|
73
|
+
return os.environ.get("LANGGRAPH_API_URL", "http://localhost:8123")
|
|
@@ -15,16 +15,14 @@ class _DummyRegModel(BaseModel):
|
|
|
15
15
|
field: str | None = None
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
async def _dummy_reg_handler(request, user_id,
|
|
18
|
+
async def _dummy_reg_handler(request, user_id, registration):
|
|
19
19
|
from langchain_triggers.core import TriggerRegistrationResult
|
|
20
20
|
|
|
21
21
|
return TriggerRegistrationResult()
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
async def _dummy_trigger_handler(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return TriggerHandlerResult(invoke_agent=False, response_body={"ok": True})
|
|
24
|
+
async def _dummy_trigger_handler(request, database):
|
|
25
|
+
return {"ok": True}
|
|
28
26
|
|
|
29
27
|
|
|
30
28
|
class _FakeDB:
|
|
@@ -30,9 +30,9 @@ class TestRegistration(BaseModel):
|
|
|
30
30
|
name: str
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
async def dummy_trigger_handler(
|
|
33
|
+
async def dummy_trigger_handler(request, database):
|
|
34
34
|
"""Dummy trigger handler for test triggers."""
|
|
35
|
-
return
|
|
35
|
+
return {"ok": True}
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
# Mock database class
|
|
@@ -399,7 +399,7 @@ async def test_metadata_storage(trigger_server):
|
|
|
399
399
|
"""Test that handler metadata is properly stored."""
|
|
400
400
|
from langchain_triggers import TriggerRegistrationResult, TriggerTemplate
|
|
401
401
|
|
|
402
|
-
async def test_registration_handler(request, user_id,
|
|
402
|
+
async def test_registration_handler(request, user_id, registration):
|
|
403
403
|
return TriggerRegistrationResult(metadata={"handler_data": "from_handler"})
|
|
404
404
|
|
|
405
405
|
test_trigger = TriggerTemplate(
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Current: 0.3.8, PyPI: 0.3.7, Should publish: True
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"""Authentication utilities for trigger webhooks."""
|
|
2
|
-
|
|
3
|
-
from .slack_hmac import (
|
|
4
|
-
SlackSignatureVerificationError,
|
|
5
|
-
extract_slack_headers,
|
|
6
|
-
get_slack_signing_secret,
|
|
7
|
-
verify_slack_signature,
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
__all__ = [
|
|
11
|
-
"verify_slack_signature",
|
|
12
|
-
"get_slack_signing_secret",
|
|
13
|
-
"extract_slack_headers",
|
|
14
|
-
"SlackSignatureVerificationError",
|
|
15
|
-
]
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
"""Slack HMAC signature verification for webhook authentication.
|
|
2
|
-
|
|
3
|
-
Slack uses HMAC-SHA256 signatures to verify webhook authenticity.
|
|
4
|
-
Each request includes an X-Slack-Signature header that must be verified
|
|
5
|
-
against your app's signing secret.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
import hashlib
|
|
11
|
-
import hmac
|
|
12
|
-
import logging
|
|
13
|
-
import os
|
|
14
|
-
import time
|
|
15
|
-
|
|
16
|
-
logger = logging.getLogger(__name__)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class SlackSignatureVerificationError(Exception):
|
|
20
|
-
"""Exception raised when Slack signature verification fails."""
|
|
21
|
-
|
|
22
|
-
pass
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def verify_slack_signature(
|
|
26
|
-
signing_secret: str,
|
|
27
|
-
timestamp: str,
|
|
28
|
-
body: str,
|
|
29
|
-
signature: str,
|
|
30
|
-
max_age_seconds: int = 300,
|
|
31
|
-
) -> bool:
|
|
32
|
-
try:
|
|
33
|
-
# Verify timestamp to prevent replay attacks
|
|
34
|
-
current_time = int(time.time())
|
|
35
|
-
request_time = int(timestamp)
|
|
36
|
-
|
|
37
|
-
if abs(current_time - request_time) > max_age_seconds:
|
|
38
|
-
logger.error(
|
|
39
|
-
f"Slack request timestamp too old. "
|
|
40
|
-
f"Current: {current_time}, Request: {request_time}, "
|
|
41
|
-
f"Diff: {abs(current_time - request_time)}s"
|
|
42
|
-
)
|
|
43
|
-
raise SlackSignatureVerificationError(
|
|
44
|
-
f"Request timestamp is too old (>{max_age_seconds}s). Possible replay attack."
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
# Format: v0:{timestamp}:{body}
|
|
48
|
-
sig_basestring = f"v0:{timestamp}:{body}"
|
|
49
|
-
|
|
50
|
-
# Create HMAC-SHA256 hash
|
|
51
|
-
my_signature = (
|
|
52
|
-
"v0="
|
|
53
|
-
+ hmac.new(
|
|
54
|
-
signing_secret.encode(), sig_basestring.encode(), hashlib.sha256
|
|
55
|
-
).hexdigest()
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
if not hmac.compare_digest(my_signature, signature):
|
|
59
|
-
logger.error(
|
|
60
|
-
f"Slack signature mismatch. Expected: {my_signature}, Got: {signature}"
|
|
61
|
-
)
|
|
62
|
-
raise SlackSignatureVerificationError("Signature verification failed")
|
|
63
|
-
|
|
64
|
-
logger.info("Successfully verified Slack webhook signature")
|
|
65
|
-
return True
|
|
66
|
-
|
|
67
|
-
except ValueError as e:
|
|
68
|
-
logger.error(f"Invalid timestamp format: {e}")
|
|
69
|
-
raise SlackSignatureVerificationError(f"Invalid timestamp: {str(e)}")
|
|
70
|
-
except Exception as e:
|
|
71
|
-
logger.error(f"Unexpected error during Slack signature verification: {e}")
|
|
72
|
-
raise SlackSignatureVerificationError(f"Verification error: {str(e)}")
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def get_slack_signing_secret() -> str | None:
|
|
76
|
-
"""Get Slack signing secret from SLACK_SIGNING_SECRET environment variable."""
|
|
77
|
-
secret = os.getenv("SLACK_SIGNING_SECRET")
|
|
78
|
-
if not secret:
|
|
79
|
-
logger.warning("SLACK_SIGNING_SECRET environment variable not set")
|
|
80
|
-
return secret
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def extract_slack_headers(headers: dict) -> tuple[str | None, str | None]:
|
|
84
|
-
"""Extract Slack signature and timestamp from request headers."""
|
|
85
|
-
signature = headers.get("x-slack-signature") or headers.get("X-Slack-Signature")
|
|
86
|
-
timestamp = headers.get("x-slack-request-timestamp") or headers.get(
|
|
87
|
-
"X-Slack-Request-Timestamp"
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
return signature, timestamp
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Current: 0.3.6, PyPI: 0.3.5, Should publish: True
|
|
File without changes
|
{langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/.github/workflows/_lint.yml
RENAMED
|
File without changes
|
{langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/.github/workflows/_test.yml
RENAMED
|
File without changes
|
|
File without changes
|
{langchain_trigger_server-0.3.6 → langchain_trigger_server-0.3.8}/.github/workflows/release.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|