airbyte-agent-slack 0.1.6__py3-none-any.whl → 0.1.8__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.
@@ -17,15 +17,30 @@ from .models import (
17
17
  ChannelPurpose,
18
18
  ChannelsListResponse,
19
19
  ChannelResponse,
20
- Attachment,
21
20
  Reaction,
22
21
  File,
22
+ Attachment,
23
23
  Message,
24
24
  Thread,
25
25
  EditedInfo,
26
26
  BotProfile,
27
27
  MessagesListResponse,
28
28
  ThreadRepliesResponse,
29
+ MessageCreateParams,
30
+ CreatedMessage,
31
+ MessageCreateResponse,
32
+ MessageUpdateParams,
33
+ MessageUpdateResponse,
34
+ ChannelCreateParams,
35
+ ChannelCreateResponse,
36
+ ChannelRenameParams,
37
+ ChannelRenameResponse,
38
+ ChannelTopicParams,
39
+ ChannelTopicResponse,
40
+ ChannelPurposeParams,
41
+ ChannelPurposeResponse,
42
+ ReactionAddParams,
43
+ ReactionAddResponse,
29
44
  UsersListResultMeta,
30
45
  ChannelsListResultMeta,
31
46
  ChannelMessagesListResultMeta,
@@ -43,7 +58,14 @@ from .types import (
43
58
  ChannelsListParams,
44
59
  ChannelsGetParams,
45
60
  ChannelMessagesListParams,
46
- ThreadsListParams
61
+ ThreadsListParams,
62
+ MessagesCreateParams,
63
+ MessagesUpdateParams,
64
+ ChannelsCreateParams,
65
+ ChannelsUpdateParams,
66
+ ChannelTopicsCreateParams,
67
+ ChannelPurposesCreateParams,
68
+ ReactionsCreateParams
47
69
  )
48
70
 
49
71
  __all__ = [
@@ -59,15 +81,30 @@ __all__ = [
59
81
  "ChannelPurpose",
60
82
  "ChannelsListResponse",
61
83
  "ChannelResponse",
62
- "Attachment",
63
84
  "Reaction",
64
85
  "File",
86
+ "Attachment",
65
87
  "Message",
66
88
  "Thread",
67
89
  "EditedInfo",
68
90
  "BotProfile",
69
91
  "MessagesListResponse",
70
92
  "ThreadRepliesResponse",
93
+ "MessageCreateParams",
94
+ "CreatedMessage",
95
+ "MessageCreateResponse",
96
+ "MessageUpdateParams",
97
+ "MessageUpdateResponse",
98
+ "ChannelCreateParams",
99
+ "ChannelCreateResponse",
100
+ "ChannelRenameParams",
101
+ "ChannelRenameResponse",
102
+ "ChannelTopicParams",
103
+ "ChannelTopicResponse",
104
+ "ChannelPurposeParams",
105
+ "ChannelPurposeResponse",
106
+ "ReactionAddParams",
107
+ "ReactionAddResponse",
71
108
  "UsersListResultMeta",
72
109
  "ChannelsListResultMeta",
73
110
  "ChannelMessagesListResultMeta",
@@ -84,4 +121,11 @@ __all__ = [
84
121
  "ChannelsGetParams",
85
122
  "ChannelMessagesListParams",
86
123
  "ThreadsListParams",
124
+ "MessagesCreateParams",
125
+ "MessagesUpdateParams",
126
+ "ChannelsCreateParams",
127
+ "ChannelsUpdateParams",
128
+ "ChannelTopicsCreateParams",
129
+ "ChannelPurposesCreateParams",
130
+ "ReactionsCreateParams",
87
131
  ]
@@ -987,7 +987,9 @@ class LocalExecutor:
987
987
 
988
988
  # Substitute variables from params
989
989
  if "variables" in graphql_config and graphql_config["variables"]:
990
- body["variables"] = self._interpolate_variables(graphql_config["variables"], params, param_defaults)
990
+ variables = self._interpolate_variables(graphql_config["variables"], params, param_defaults)
991
+ # Filter out None values (optional fields not provided) - matches REST _extract_body() behavior
992
+ body["variables"] = {k: v for k, v in variables.items() if v is not None}
991
993
 
992
994
  # Add operation name if specified
993
995
  if "operationName" in graphql_config:
@@ -478,6 +478,7 @@ class HTTPClient:
478
478
  request_id=request_id,
479
479
  status_code=status_code,
480
480
  response_body=f"<binary content, {response.headers.get('content-length', 'unknown')} bytes>",
481
+ response_headers=dict(response.headers),
481
482
  )
482
483
  return response, dict(response.headers)
483
484
 
@@ -504,6 +505,7 @@ class HTTPClient:
504
505
  request_id=request_id,
505
506
  status_code=status_code,
506
507
  response_body=response_data,
508
+ response_headers=dict(response.headers),
507
509
  )
508
510
  return response_data, dict(response.headers)
509
511
 
@@ -134,6 +134,7 @@ class RequestLogger:
134
134
  request_id: str,
135
135
  status_code: int,
136
136
  response_body: Any | None = None,
137
+ response_headers: Dict[str, str] | None = None,
137
138
  ) -> None:
138
139
  """
139
140
  Log a successful HTTP response.
@@ -142,6 +143,7 @@ class RequestLogger:
142
143
  request_id: ID returned from log_request
143
144
  status_code: HTTP status code
144
145
  response_body: Response body
146
+ response_headers: Response headers
145
147
  """
146
148
  if request_id not in self._active_requests:
147
149
  return
@@ -166,6 +168,7 @@ class RequestLogger:
166
168
  body=request_data["body"],
167
169
  response_status=status_code,
168
170
  response_body=serializable_body,
171
+ response_headers=response_headers or {},
169
172
  timing_ms=timing_ms,
170
173
  )
171
174
 
@@ -243,7 +246,13 @@ class NullLogger:
243
246
  """No-op log_request."""
244
247
  return ""
245
248
 
246
- def log_response(self, *args, **kwargs) -> None:
249
+ def log_response(
250
+ self,
251
+ request_id: str,
252
+ status_code: int,
253
+ response_body: Any | None = None,
254
+ response_headers: Dict[str, str] | None = None,
255
+ ) -> None:
247
256
  """No-op log_response."""
248
257
  pass
249
258
 
@@ -31,6 +31,7 @@ class RequestLog(BaseModel):
31
31
  body: Any | None = None
32
32
  response_status: int | None = None
33
33
  response_body: Any | None = None
34
+ response_headers: Dict[str, str] = Field(default_factory=dict)
34
35
  timing_ms: float | None = None
35
36
  error: str | None = None
36
37
 
@@ -486,30 +486,36 @@ def validate_meta_extractor_fields(
486
486
  response_body = spec.captured_response.body
487
487
 
488
488
  # Validate each meta extractor field
489
- for field_name, jsonpath_expr in endpoint.meta_extractor.items():
489
+ for field_name, extractor_expr in endpoint.meta_extractor.items():
490
+ # Skip header-based extractors - they extract from headers, not response body
491
+ # @link.next extracts from RFC 5988 Link header
492
+ # @header.X-Name extracts raw header value
493
+ if extractor_expr.startswith("@link.") or extractor_expr.startswith("@header."):
494
+ continue
495
+
490
496
  # Check 1: Does the JSONPath find data in the actual response?
491
497
  try:
492
- parsed_expr = parse_jsonpath(jsonpath_expr)
498
+ parsed_expr = parse_jsonpath(extractor_expr)
493
499
  matches = [match.value for match in parsed_expr.find(response_body)]
494
500
 
495
501
  if not matches:
496
502
  warnings.append(
497
503
  f"{entity_name}.{action}: x-airbyte-meta-extractor field '{field_name}' "
498
- f"with JSONPath '{jsonpath_expr}' found no matches in cassette response"
504
+ f"with JSONPath '{extractor_expr}' found no matches in cassette response"
499
505
  )
500
506
  except Exception as e:
501
507
  warnings.append(
502
- f"{entity_name}.{action}: x-airbyte-meta-extractor field '{field_name}' has invalid JSONPath '{jsonpath_expr}': {str(e)}"
508
+ f"{entity_name}.{action}: x-airbyte-meta-extractor field '{field_name}' has invalid JSONPath '{extractor_expr}': {str(e)}"
503
509
  )
504
510
 
505
511
  # Check 2: Is this field path declared in the response schema?
506
512
  if endpoint.response_schema:
507
- field_in_schema = _check_field_in_schema(jsonpath_expr, endpoint.response_schema)
513
+ field_in_schema = _check_field_in_schema(extractor_expr, endpoint.response_schema)
508
514
 
509
515
  if not field_in_schema:
510
516
  warnings.append(
511
517
  f"{entity_name}.{action}: x-airbyte-meta-extractor field '{field_name}' "
512
- f"extracts from '{jsonpath_expr}' but this path is not declared in response schema"
518
+ f"extracts from '{extractor_expr}' but this path is not declared in response schema"
513
519
  )
514
520
 
515
521
  except Exception as e:
@@ -15,8 +15,15 @@ from .connector_model import SlackConnectorModel
15
15
  from ._vendored.connector_sdk.introspection import describe_entities, generate_tool_description
16
16
  from .types import (
17
17
  ChannelMessagesListParams,
18
+ ChannelPurposesCreateParams,
19
+ ChannelTopicsCreateParams,
20
+ ChannelsCreateParams,
18
21
  ChannelsGetParams,
19
22
  ChannelsListParams,
23
+ ChannelsUpdateParams,
24
+ MessagesCreateParams,
25
+ MessagesUpdateParams,
26
+ ReactionsCreateParams,
20
27
  ThreadsListParams,
21
28
  UsersGetParams,
22
29
  UsersListParams,
@@ -34,7 +41,9 @@ from .models import (
34
41
  ChannelMessagesListResult,
35
42
  ThreadsListResult,
36
43
  Channel,
44
+ CreatedMessage,
37
45
  Message,
46
+ ReactionAddResponse,
38
47
  Thread,
39
48
  User,
40
49
  )
@@ -52,7 +61,7 @@ class SlackConnector:
52
61
  """
53
62
 
54
63
  connector_name = "slack"
55
- connector_version = "0.1.1"
64
+ connector_version = "0.1.2"
56
65
  vendored_sdk_version = "0.1.0" # Version of vendored connector-sdk
57
66
 
58
67
  # Map of (entity, action) -> needs_envelope for envelope wrapping decision
@@ -63,6 +72,13 @@ class SlackConnector:
63
72
  ("channels", "get"): None,
64
73
  ("channel_messages", "list"): True,
65
74
  ("threads", "list"): True,
75
+ ("messages", "create"): None,
76
+ ("messages", "update"): None,
77
+ ("channels", "create"): None,
78
+ ("channels", "update"): None,
79
+ ("channel_topics", "create"): None,
80
+ ("channel_purposes", "create"): None,
81
+ ("reactions", "create"): None,
66
82
  }
67
83
 
68
84
  # Map of (entity, action) -> {python_param_name: api_param_name}
@@ -74,6 +90,13 @@ class SlackConnector:
74
90
  ('channels', 'get'): {'channel': 'channel'},
75
91
  ('channel_messages', 'list'): {'channel': 'channel', 'cursor': 'cursor', 'limit': 'limit', 'oldest': 'oldest', 'latest': 'latest', 'inclusive': 'inclusive'},
76
92
  ('threads', 'list'): {'channel': 'channel', 'ts': 'ts', 'cursor': 'cursor', 'limit': 'limit', 'oldest': 'oldest', 'latest': 'latest', 'inclusive': 'inclusive'},
93
+ ('messages', 'create'): {'channel': 'channel', 'text': 'text', 'thread_ts': 'thread_ts', 'reply_broadcast': 'reply_broadcast', 'unfurl_links': 'unfurl_links', 'unfurl_media': 'unfurl_media'},
94
+ ('messages', 'update'): {'channel': 'channel', 'ts': 'ts', 'text': 'text'},
95
+ ('channels', 'create'): {'name': 'name', 'is_private': 'is_private'},
96
+ ('channels', 'update'): {'channel': 'channel', 'name': 'name'},
97
+ ('channel_topics', 'create'): {'channel': 'channel', 'topic': 'topic'},
98
+ ('channel_purposes', 'create'): {'channel': 'channel', 'purpose': 'purpose'},
99
+ ('reactions', 'create'): {'channel': 'channel', 'timestamp': 'timestamp', 'name': 'name'},
77
100
  }
78
101
 
79
102
  def __init__(
@@ -164,6 +187,10 @@ class SlackConnector:
164
187
  self.channels = ChannelsQuery(self)
165
188
  self.channel_messages = ChannelMessagesQuery(self)
166
189
  self.threads = ThreadsQuery(self)
190
+ self.messages = MessagesQuery(self)
191
+ self.channel_topics = ChannelTopicsQuery(self)
192
+ self.channel_purposes = ChannelPurposesQuery(self)
193
+ self.reactions = ReactionsQuery(self)
167
194
 
168
195
  # ===== TYPED EXECUTE METHOD (Recommended Interface) =====
169
196
 
@@ -215,6 +242,62 @@ class SlackConnector:
215
242
  params: "ThreadsListParams"
216
243
  ) -> "ThreadsListResult": ...
217
244
 
245
+ @overload
246
+ async def execute(
247
+ self,
248
+ entity: Literal["messages"],
249
+ action: Literal["create"],
250
+ params: "MessagesCreateParams"
251
+ ) -> "CreatedMessage": ...
252
+
253
+ @overload
254
+ async def execute(
255
+ self,
256
+ entity: Literal["messages"],
257
+ action: Literal["update"],
258
+ params: "MessagesUpdateParams"
259
+ ) -> "CreatedMessage": ...
260
+
261
+ @overload
262
+ async def execute(
263
+ self,
264
+ entity: Literal["channels"],
265
+ action: Literal["create"],
266
+ params: "ChannelsCreateParams"
267
+ ) -> "Channel": ...
268
+
269
+ @overload
270
+ async def execute(
271
+ self,
272
+ entity: Literal["channels"],
273
+ action: Literal["update"],
274
+ params: "ChannelsUpdateParams"
275
+ ) -> "Channel": ...
276
+
277
+ @overload
278
+ async def execute(
279
+ self,
280
+ entity: Literal["channel_topics"],
281
+ action: Literal["create"],
282
+ params: "ChannelTopicsCreateParams"
283
+ ) -> "Channel": ...
284
+
285
+ @overload
286
+ async def execute(
287
+ self,
288
+ entity: Literal["channel_purposes"],
289
+ action: Literal["create"],
290
+ params: "ChannelPurposesCreateParams"
291
+ ) -> "Channel": ...
292
+
293
+ @overload
294
+ async def execute(
295
+ self,
296
+ entity: Literal["reactions"],
297
+ action: Literal["create"],
298
+ params: "ReactionsCreateParams"
299
+ ) -> "ReactionAddResponse": ...
300
+
218
301
 
219
302
  @overload
220
303
  async def execute(
@@ -511,6 +594,62 @@ class ChannelsQuery:
511
594
 
512
595
 
513
596
 
597
+ async def create(
598
+ self,
599
+ name: str,
600
+ is_private: bool | None = None,
601
+ **kwargs
602
+ ) -> Channel:
603
+ """
604
+ Creates a new public or private channel
605
+
606
+ Args:
607
+ name: Channel name (lowercase, no spaces, max 80 chars)
608
+ is_private: Create a private channel instead of public
609
+ **kwargs: Additional parameters
610
+
611
+ Returns:
612
+ Channel
613
+ """
614
+ params = {k: v for k, v in {
615
+ "name": name,
616
+ "is_private": is_private,
617
+ **kwargs
618
+ }.items() if v is not None}
619
+
620
+ result = await self._connector.execute("channels", "create", params)
621
+ return result
622
+
623
+
624
+
625
+ async def update(
626
+ self,
627
+ channel: str,
628
+ name: str,
629
+ **kwargs
630
+ ) -> Channel:
631
+ """
632
+ Renames an existing channel
633
+
634
+ Args:
635
+ channel: Channel ID to rename
636
+ name: New channel name (lowercase, no spaces, max 80 chars)
637
+ **kwargs: Additional parameters
638
+
639
+ Returns:
640
+ Channel
641
+ """
642
+ params = {k: v for k, v in {
643
+ "channel": channel,
644
+ "name": name,
645
+ **kwargs
646
+ }.items() if v is not None}
647
+
648
+ result = await self._connector.execute("channels", "update", params)
649
+ return result
650
+
651
+
652
+
514
653
  class ChannelMessagesQuery:
515
654
  """
516
655
  Query class for ChannelMessages entity operations.
@@ -619,3 +758,197 @@ class ThreadsQuery:
619
758
  )
620
759
 
621
760
 
761
+
762
+ class MessagesQuery:
763
+ """
764
+ Query class for Messages entity operations.
765
+ """
766
+
767
+ def __init__(self, connector: SlackConnector):
768
+ """Initialize query with connector reference."""
769
+ self._connector = connector
770
+
771
+ async def create(
772
+ self,
773
+ channel: str,
774
+ text: str,
775
+ thread_ts: str | None = None,
776
+ reply_broadcast: bool | None = None,
777
+ unfurl_links: bool | None = None,
778
+ unfurl_media: bool | None = None,
779
+ **kwargs
780
+ ) -> CreatedMessage:
781
+ """
782
+ Posts a message to a public channel, private channel, or direct message conversation
783
+
784
+ Args:
785
+ channel: Channel ID, private group ID, or user ID to send message to
786
+ text: Message text content (supports mrkdwn formatting)
787
+ thread_ts: Thread timestamp to reply to (for threaded messages)
788
+ reply_broadcast: Also post reply to channel when replying to a thread
789
+ unfurl_links: Enable unfurling of primarily text-based content
790
+ unfurl_media: Enable unfurling of media content
791
+ **kwargs: Additional parameters
792
+
793
+ Returns:
794
+ CreatedMessage
795
+ """
796
+ params = {k: v for k, v in {
797
+ "channel": channel,
798
+ "text": text,
799
+ "thread_ts": thread_ts,
800
+ "reply_broadcast": reply_broadcast,
801
+ "unfurl_links": unfurl_links,
802
+ "unfurl_media": unfurl_media,
803
+ **kwargs
804
+ }.items() if v is not None}
805
+
806
+ result = await self._connector.execute("messages", "create", params)
807
+ return result
808
+
809
+
810
+
811
+ async def update(
812
+ self,
813
+ channel: str,
814
+ ts: str,
815
+ text: str,
816
+ **kwargs
817
+ ) -> CreatedMessage:
818
+ """
819
+ Updates an existing message in a channel
820
+
821
+ Args:
822
+ channel: Channel ID containing the message
823
+ ts: Timestamp of the message to update
824
+ text: New message text content
825
+ **kwargs: Additional parameters
826
+
827
+ Returns:
828
+ CreatedMessage
829
+ """
830
+ params = {k: v for k, v in {
831
+ "channel": channel,
832
+ "ts": ts,
833
+ "text": text,
834
+ **kwargs
835
+ }.items() if v is not None}
836
+
837
+ result = await self._connector.execute("messages", "update", params)
838
+ return result
839
+
840
+
841
+
842
+ class ChannelTopicsQuery:
843
+ """
844
+ Query class for ChannelTopics entity operations.
845
+ """
846
+
847
+ def __init__(self, connector: SlackConnector):
848
+ """Initialize query with connector reference."""
849
+ self._connector = connector
850
+
851
+ async def create(
852
+ self,
853
+ channel: str,
854
+ topic: str,
855
+ **kwargs
856
+ ) -> Channel:
857
+ """
858
+ Sets the topic for a channel
859
+
860
+ Args:
861
+ channel: Channel ID to set topic for
862
+ topic: New topic text (max 250 characters)
863
+ **kwargs: Additional parameters
864
+
865
+ Returns:
866
+ Channel
867
+ """
868
+ params = {k: v for k, v in {
869
+ "channel": channel,
870
+ "topic": topic,
871
+ **kwargs
872
+ }.items() if v is not None}
873
+
874
+ result = await self._connector.execute("channel_topics", "create", params)
875
+ return result
876
+
877
+
878
+
879
+ class ChannelPurposesQuery:
880
+ """
881
+ Query class for ChannelPurposes entity operations.
882
+ """
883
+
884
+ def __init__(self, connector: SlackConnector):
885
+ """Initialize query with connector reference."""
886
+ self._connector = connector
887
+
888
+ async def create(
889
+ self,
890
+ channel: str,
891
+ purpose: str,
892
+ **kwargs
893
+ ) -> Channel:
894
+ """
895
+ Sets the purpose for a channel
896
+
897
+ Args:
898
+ channel: Channel ID to set purpose for
899
+ purpose: New purpose text (max 250 characters)
900
+ **kwargs: Additional parameters
901
+
902
+ Returns:
903
+ Channel
904
+ """
905
+ params = {k: v for k, v in {
906
+ "channel": channel,
907
+ "purpose": purpose,
908
+ **kwargs
909
+ }.items() if v is not None}
910
+
911
+ result = await self._connector.execute("channel_purposes", "create", params)
912
+ return result
913
+
914
+
915
+
916
+ class ReactionsQuery:
917
+ """
918
+ Query class for Reactions entity operations.
919
+ """
920
+
921
+ def __init__(self, connector: SlackConnector):
922
+ """Initialize query with connector reference."""
923
+ self._connector = connector
924
+
925
+ async def create(
926
+ self,
927
+ channel: str,
928
+ timestamp: str,
929
+ name: str,
930
+ **kwargs
931
+ ) -> ReactionAddResponse:
932
+ """
933
+ Adds a reaction (emoji) to a message
934
+
935
+ Args:
936
+ channel: Channel ID containing the message
937
+ timestamp: Timestamp of the message to react to
938
+ name: Reaction emoji name (without colons, e.g., "thumbsup")
939
+ **kwargs: Additional parameters
940
+
941
+ Returns:
942
+ ReactionAddResponse
943
+ """
944
+ params = {k: v for k, v in {
945
+ "channel": channel,
946
+ "timestamp": timestamp,
947
+ "name": name,
948
+ **kwargs
949
+ }.items() if v is not None}
950
+
951
+ result = await self._connector.execute("reactions", "create", params)
952
+ return result
953
+
954
+