airbyte-agent-slack 0.1.2__tar.gz → 0.1.7__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.
Files changed (61) hide show
  1. airbyte_agent_slack-0.1.7/CHANGELOG.md +41 -0
  2. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/PKG-INFO +3 -3
  3. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/README.md +2 -2
  4. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/REFERENCE.md +12 -10
  5. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/__init__.py +2 -2
  6. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/executor/local_executor.py +112 -21
  7. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/http_client.py +13 -6
  8. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/logging/logger.py +10 -1
  9. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/logging/types.py +1 -0
  10. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/validation.py +12 -6
  11. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/connector.py +1 -0
  12. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/models.py +9 -8
  13. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/types.py +1 -0
  14. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/pyproject.toml +1 -1
  15. airbyte_agent_slack-0.1.2/CHANGELOG.md +0 -16
  16. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/.gitignore +0 -0
  17. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/__init__.py +0 -0
  18. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/__init__.py +0 -0
  19. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/auth_strategies.py +0 -0
  20. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/auth_template.py +0 -0
  21. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/cloud_utils/__init__.py +0 -0
  22. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/cloud_utils/client.py +0 -0
  23. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/connector_model_loader.py +0 -0
  24. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/constants.py +0 -0
  25. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/exceptions.py +0 -0
  26. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/executor/__init__.py +0 -0
  27. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/executor/hosted_executor.py +0 -0
  28. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/executor/models.py +0 -0
  29. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/extensions.py +0 -0
  30. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/http/__init__.py +0 -0
  31. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/http/adapters/__init__.py +0 -0
  32. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/http/adapters/httpx_adapter.py +0 -0
  33. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/http/config.py +0 -0
  34. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/http/exceptions.py +0 -0
  35. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/http/protocols.py +0 -0
  36. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/http/response.py +0 -0
  37. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/introspection.py +0 -0
  38. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/logging/__init__.py +0 -0
  39. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/observability/__init__.py +0 -0
  40. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/observability/config.py +0 -0
  41. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/observability/models.py +0 -0
  42. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/observability/redactor.py +0 -0
  43. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/observability/session.py +0 -0
  44. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/performance/__init__.py +0 -0
  45. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/performance/instrumentation.py +0 -0
  46. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/performance/metrics.py +0 -0
  47. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/schema/__init__.py +0 -0
  48. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/schema/base.py +0 -0
  49. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/schema/components.py +0 -0
  50. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/schema/connector.py +0 -0
  51. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/schema/extensions.py +0 -0
  52. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/schema/operations.py +0 -0
  53. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/schema/security.py +0 -0
  54. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/secrets.py +0 -0
  55. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/telemetry/__init__.py +0 -0
  56. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/telemetry/config.py +0 -0
  57. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/telemetry/events.py +0 -0
  58. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/telemetry/tracker.py +0 -0
  59. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/types.py +0 -0
  60. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/_vendored/connector_sdk/utils.py +0 -0
  61. {airbyte_agent_slack-0.1.2 → airbyte_agent_slack-0.1.7}/airbyte_agent_slack/connector_model.py +0 -0
@@ -0,0 +1,41 @@
1
+ # Slack changelog
2
+
3
+ ## [0.1.7] - 2026-01-19
4
+ - Updated connector definition (YAML version 0.1.1)
5
+ - Source commit: 529cebb7
6
+ - SDK version: 0.1.0
7
+
8
+ ## [0.1.6] - 2026-01-16
9
+ - Updated connector definition (YAML version 0.1.1)
10
+ - Source commit: a50c8f71
11
+ - SDK version: 0.1.0
12
+
13
+ ## [0.1.5] - 2026-01-16
14
+ - Updated connector definition (YAML version 0.1.1)
15
+ - Source commit: 49673b7b
16
+ - SDK version: 0.1.0
17
+
18
+ ## [0.1.4] - 2026-01-16
19
+ - Updated connector definition (YAML version 0.1.1)
20
+ - Source commit: ca5acdda
21
+ - SDK version: 0.1.0
22
+
23
+ ## [0.1.3] - 2026-01-15
24
+ - Updated connector definition (YAML version 0.1.1)
25
+ - Source commit: fa9a3b02
26
+ - SDK version: 0.1.0
27
+
28
+ ## [0.1.2] - 2026-01-15
29
+ - Updated connector definition (YAML version 0.1.1)
30
+ - Source commit: 61a2e822
31
+ - SDK version: 0.1.0
32
+
33
+ ## [0.1.1] - 2026-01-15
34
+ - Updated connector definition (YAML version 0.1.1)
35
+ - Source commit: 35211193
36
+ - SDK version: 0.1.0
37
+
38
+ ## [0.1.0] - 2026-01-15
39
+ - Updated connector definition (YAML version 0.1.1)
40
+ - Source commit: 8b64ece5
41
+ - SDK version: 0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: airbyte-agent-slack
3
- Version: 0.1.2
3
+ Version: 0.1.7
4
4
  Summary: Airbyte Slack Connector for AI platforms
5
5
  Project-URL: Homepage, https://github.com/airbytehq/airbyte-agent-connectors
6
6
  Project-URL: Documentation, https://docs.airbyte.com/ai-agents/
@@ -123,6 +123,6 @@ For the service's official API docs, see the [Slack API reference](https://api.s
123
123
 
124
124
  ## Version information
125
125
 
126
- - **Package version:** 0.1.2
126
+ - **Package version:** 0.1.7
127
127
  - **Connector version:** 0.1.1
128
- - **Generated with Connector SDK commit SHA:** 61a2e8229a38f13564ef2f85e276dff02f707573
128
+ - **Generated with Connector SDK commit SHA:** c46670b9e4ca5238c0372e143b44088a0d1a68ee
@@ -90,6 +90,6 @@ For the service's official API docs, see the [Slack API reference](https://api.s
90
90
 
91
91
  ## Version information
92
92
 
93
- - **Package version:** 0.1.2
93
+ - **Package version:** 0.1.7
94
94
  - **Connector version:** 0.1.1
95
- - **Generated with Connector SDK commit SHA:** 61a2e8229a38f13564ef2f85e276dff02f707573
95
+ - **Generated with Connector SDK commit SHA:** c46670b9e4ca5238c0372e143b44088a0d1a68ee
@@ -28,7 +28,7 @@ await slack.users.list()
28
28
  **API**
29
29
 
30
30
  ```bash
31
- curl --location 'https://api.airbyte.ai/api/v1/connectors/instances/{your_connector_instance_id}/execute' \
31
+ curl --location 'https://api.airbyte.ai/api/v1/connectors/sources/{your_source_id}/execute' \
32
32
  --header 'Content-Type: application/json' \
33
33
  --header 'Authorization: Bearer {your_auth_token}' \
34
34
  --data '{
@@ -98,7 +98,7 @@ await slack.users.get(
98
98
  **API**
99
99
 
100
100
  ```bash
101
- curl --location 'https://api.airbyte.ai/api/v1/connectors/instances/{your_connector_instance_id}/execute' \
101
+ curl --location 'https://api.airbyte.ai/api/v1/connectors/sources/{your_source_id}/execute' \
102
102
  --header 'Content-Type: application/json' \
103
103
  --header 'Authorization: Bearer {your_auth_token}' \
104
104
  --data '{
@@ -164,7 +164,7 @@ await slack.channels.list()
164
164
  **API**
165
165
 
166
166
  ```bash
167
- curl --location 'https://api.airbyte.ai/api/v1/connectors/instances/{your_connector_instance_id}/execute' \
167
+ curl --location 'https://api.airbyte.ai/api/v1/connectors/sources/{your_source_id}/execute' \
168
168
  --header 'Content-Type: application/json' \
169
169
  --header 'Authorization: Bearer {your_auth_token}' \
170
170
  --data '{
@@ -247,7 +247,7 @@ await slack.channels.get(
247
247
  **API**
248
248
 
249
249
  ```bash
250
- curl --location 'https://api.airbyte.ai/api/v1/connectors/instances/{your_connector_instance_id}/execute' \
250
+ curl --location 'https://api.airbyte.ai/api/v1/connectors/sources/{your_source_id}/execute' \
251
251
  --header 'Content-Type: application/json' \
252
252
  --header 'Authorization: Bearer {your_auth_token}' \
253
253
  --data '{
@@ -326,7 +326,7 @@ await slack.channel_messages.list(
326
326
  **API**
327
327
 
328
328
  ```bash
329
- curl --location 'https://api.airbyte.ai/api/v1/connectors/instances/{your_connector_instance_id}/execute' \
329
+ curl --location 'https://api.airbyte.ai/api/v1/connectors/sources/{your_source_id}/execute' \
330
330
  --header 'Content-Type: application/json' \
331
331
  --header 'Authorization: Bearer {your_auth_token}' \
332
332
  --data '{
@@ -445,7 +445,7 @@ await slack.threads.list(
445
445
  **API**
446
446
 
447
447
  ```bash
448
- curl --location 'https://api.airbyte.ai/api/v1/connectors/instances/{your_connector_instance_id}/execute' \
448
+ curl --location 'https://api.airbyte.ai/api/v1/connectors/sources/{your_source_id}/execute' \
449
449
  --header 'Content-Type: application/json' \
450
450
  --header 'Authorization: Bearer {your_auth_token}' \
451
451
  --data '{
@@ -577,11 +577,12 @@ SlackConnector(
577
577
  **API**
578
578
 
579
579
  ```bash
580
- curl --location 'https://api.airbyte.ai/api/v1/connectors/instances' \
580
+ curl --location 'https://api.airbyte.ai/api/v1/integrations/sources' \
581
581
  --header 'Content-Type: application/json' \
582
582
  --header 'Authorization: Bearer {your_auth_token}' \
583
583
  --data '{
584
- "connector_definition_id": "c2281cee-86f9-4a86-bb48-d23286b4c7bd",
584
+ "workspace_id": "{your_workspace_id}",
585
+ "source_template_id": "{source_template_id}",
585
586
  "auth_config": {
586
587
  "access_token": "<Your Slack Bot Token (xoxb-) or User Token (xoxp-)>"
587
588
  },
@@ -615,11 +616,12 @@ SlackConnector(
615
616
  **API**
616
617
 
617
618
  ```bash
618
- curl --location 'https://api.airbyte.ai/api/v1/connectors/instances' \
619
+ curl --location 'https://api.airbyte.ai/api/v1/integrations/sources' \
619
620
  --header 'Content-Type: application/json' \
620
621
  --header 'Authorization: Bearer {your_auth_token}' \
621
622
  --data '{
622
- "connector_definition_id": "c2281cee-86f9-4a86-bb48-d23286b4c7bd",
623
+ "workspace_id": "{your_workspace_id}",
624
+ "source_template_id": "{source_template_id}",
623
625
  "auth_config": {
624
626
  "client_id": "<Your Slack App's Client ID>",
625
627
  "client_secret": "<Your Slack App's Client Secret>",
@@ -17,9 +17,9 @@ from .models import (
17
17
  ChannelPurpose,
18
18
  ChannelsListResponse,
19
19
  ChannelResponse,
20
- Reaction,
21
20
  Attachment,
22
21
  File,
22
+ Reaction,
23
23
  Message,
24
24
  Thread,
25
25
  EditedInfo,
@@ -59,9 +59,9 @@ __all__ = [
59
59
  "ChannelPurpose",
60
60
  "ChannelsListResponse",
61
61
  "ChannelResponse",
62
- "Reaction",
63
62
  "Attachment",
64
63
  "File",
64
+ "Reaction",
65
65
  "Message",
66
66
  "Thread",
67
67
  "EditedInfo",
@@ -495,6 +495,14 @@ class LocalExecutor:
495
495
  print(result.data)
496
496
  """
497
497
  try:
498
+ # Check for hosted-only actions before converting to Action enum
499
+ if config.action == "search":
500
+ raise NotImplementedError(
501
+ "search is only available in hosted execution mode. "
502
+ "Initialize the connector with external_user_id, airbyte_client_id, "
503
+ "and airbyte_client_secret to use this feature."
504
+ )
505
+
498
506
  # Convert config to internal format
499
507
  action = Action(config.action) if isinstance(config.action, str) else config.action
500
508
  params = config.params or {}
@@ -979,7 +987,9 @@ class LocalExecutor:
979
987
 
980
988
  # Substitute variables from params
981
989
  if "variables" in graphql_config and graphql_config["variables"]:
982
- 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}
983
993
 
984
994
  # Add operation name if specified
985
995
  if "operationName" in graphql_config:
@@ -1214,15 +1224,22 @@ class LocalExecutor:
1214
1224
  def _extract_metadata(
1215
1225
  self,
1216
1226
  response_data: dict[str, Any],
1227
+ response_headers: dict[str, str],
1217
1228
  endpoint: EndpointDefinition,
1218
1229
  ) -> dict[str, Any] | None:
1219
1230
  """Extract metadata from response using meta extractor.
1220
1231
 
1221
- Each field in meta_extractor dict is independently extracted using JSONPath.
1232
+ Each field in meta_extractor dict is independently extracted using JSONPath
1233
+ for body extraction, or special prefixes for header extraction:
1234
+ - @link.{rel}: Extract URL from RFC 5988 Link header by rel type
1235
+ - @header.{name}: Extract raw header value by header name
1236
+ - Otherwise: JSONPath expression for body extraction
1237
+
1222
1238
  Missing or invalid paths result in None for that field (no crash).
1223
1239
 
1224
1240
  Args:
1225
1241
  response_data: Full API response (before record extraction)
1242
+ response_headers: HTTP response headers
1226
1243
  endpoint: Endpoint with optional meta extractor configuration
1227
1244
 
1228
1245
  Returns:
@@ -1233,11 +1250,15 @@ class LocalExecutor:
1233
1250
  Example:
1234
1251
  meta_extractor = {
1235
1252
  "pagination": "$.records",
1236
- "request_id": "$.requestId"
1253
+ "request_id": "$.requestId",
1254
+ "next_page_url": "@link.next",
1255
+ "rate_limit": "@header.X-RateLimit-Remaining"
1237
1256
  }
1238
1257
  Returns: {
1239
1258
  "pagination": {"cursor": "abc", "total": 100},
1240
- "request_id": "xyz123"
1259
+ "request_id": "xyz123",
1260
+ "next_page_url": "https://api.example.com/data?cursor=abc",
1261
+ "rate_limit": "99"
1241
1262
  }
1242
1263
  """
1243
1264
  # Check if endpoint has meta extractor
@@ -1247,26 +1268,96 @@ class LocalExecutor:
1247
1268
  extracted_meta: dict[str, Any] = {}
1248
1269
 
1249
1270
  # Extract each field independently
1250
- for field_name, jsonpath_expr_str in endpoint.meta_extractor.items():
1271
+ for field_name, extractor_expr in endpoint.meta_extractor.items():
1251
1272
  try:
1252
- # Parse and apply JSONPath expression
1253
- jsonpath_expr = parse_jsonpath(jsonpath_expr_str)
1254
- matches = [match.value for match in jsonpath_expr.find(response_data)]
1255
-
1256
- if matches:
1257
- # Return first match (most common case)
1258
- extracted_meta[field_name] = matches[0]
1273
+ if extractor_expr.startswith("@link."):
1274
+ # RFC 5988 Link header extraction
1275
+ rel = extractor_expr[6:]
1276
+ extracted_meta[field_name] = self._extract_link_url(response_headers, rel)
1277
+ elif extractor_expr.startswith("@header."):
1278
+ # Raw header value extraction (case-insensitive lookup)
1279
+ header_name = extractor_expr[8:]
1280
+ extracted_meta[field_name] = self._get_header_value(response_headers, header_name)
1259
1281
  else:
1260
- # Path not found - set to None
1261
- extracted_meta[field_name] = None
1282
+ # JSONPath body extraction
1283
+ jsonpath_expr = parse_jsonpath(extractor_expr)
1284
+ matches = [match.value for match in jsonpath_expr.find(response_data)]
1285
+
1286
+ if matches:
1287
+ # Return first match (most common case)
1288
+ extracted_meta[field_name] = matches[0]
1289
+ else:
1290
+ # Path not found - set to None
1291
+ extracted_meta[field_name] = None
1262
1292
 
1263
1293
  except Exception as e:
1264
1294
  # Log error but continue with other fields
1265
- logging.warning(f"Failed to apply meta extractor for field '{field_name}' with path '{jsonpath_expr_str}': {e}. Setting to None.")
1295
+ logging.warning(f"Failed to apply meta extractor for field '{field_name}' with expression '{extractor_expr}': {e}. Setting to None.")
1266
1296
  extracted_meta[field_name] = None
1267
1297
 
1268
1298
  return extracted_meta
1269
1299
 
1300
+ @staticmethod
1301
+ def _extract_link_url(headers: dict[str, str], rel: str) -> str | None:
1302
+ """Extract URL from RFC 5988 Link header by rel type.
1303
+
1304
+ Parses Link header format: <url>; param1="value1"; rel="next"; param2="value2"
1305
+
1306
+ Supports:
1307
+ - Multiple parameters per link in any order
1308
+ - Both quoted and unquoted rel values
1309
+ - Multiple links separated by commas
1310
+
1311
+ Args:
1312
+ headers: Response headers dict
1313
+ rel: The rel type to extract (e.g., "next", "prev", "first", "last")
1314
+
1315
+ Returns:
1316
+ The URL for the specified rel type, or None if not found
1317
+ """
1318
+ link_header = headers.get("Link") or headers.get("link", "")
1319
+ if not link_header:
1320
+ return None
1321
+
1322
+ for link_segment in re.split(r",(?=\s*<)", link_header):
1323
+ link_segment = link_segment.strip()
1324
+
1325
+ url_match = re.match(r"<([^>]+)>", link_segment)
1326
+ if not url_match:
1327
+ continue
1328
+
1329
+ url = url_match.group(1)
1330
+ params_str = link_segment[url_match.end() :]
1331
+
1332
+ rel_match = re.search(r';\s*rel="?([^";,]+)"?', params_str, re.IGNORECASE)
1333
+ if rel_match and rel_match.group(1).strip() == rel:
1334
+ return url
1335
+
1336
+ return None
1337
+
1338
+ @staticmethod
1339
+ def _get_header_value(headers: dict[str, str], header_name: str) -> str | None:
1340
+ """Get header value with case-insensitive lookup.
1341
+
1342
+ Args:
1343
+ headers: Response headers dict
1344
+ header_name: Header name to look up
1345
+
1346
+ Returns:
1347
+ Header value or None if not found
1348
+ """
1349
+ # Try exact match first
1350
+ if header_name in headers:
1351
+ return headers[header_name]
1352
+
1353
+ # Case-insensitive lookup
1354
+ header_name_lower = header_name.lower()
1355
+ for key, value in headers.items():
1356
+ if key.lower() == header_name_lower:
1357
+ return value
1358
+
1359
+ return None
1360
+
1270
1361
  def _validate_required_body_fields(self, endpoint: Any, params: dict[str, Any], action: Action, entity: str) -> None:
1271
1362
  """Validate that required body fields are present for CREATE/UPDATE operations.
1272
1363
 
@@ -1394,7 +1485,7 @@ class _StandardOperationHandler:
1394
1485
  request_kwargs = self.ctx.determine_request_format(endpoint, body)
1395
1486
 
1396
1487
  # Execute async HTTP request
1397
- response = await self.ctx.http_client.request(
1488
+ response_data, response_headers = await self.ctx.http_client.request(
1398
1489
  method=endpoint.method,
1399
1490
  path=path,
1400
1491
  params=query_params if query_params else None,
@@ -1403,10 +1494,10 @@ class _StandardOperationHandler:
1403
1494
  )
1404
1495
 
1405
1496
  # Extract metadata from original response (before record extraction)
1406
- metadata = self.ctx.executor._extract_metadata(response, endpoint)
1497
+ metadata = self.ctx.executor._extract_metadata(response_data, response_headers, endpoint)
1407
1498
 
1408
1499
  # Extract records if extractor configured
1409
- response = self.ctx.extract_records(response, endpoint)
1500
+ response = self.ctx.extract_records(response_data, endpoint)
1410
1501
 
1411
1502
  # Assume success with 200 status code if no exception raised
1412
1503
  status_code = 200
@@ -1532,7 +1623,7 @@ class _DownloadOperationHandler:
1532
1623
  request_format = self.ctx.determine_request_format(operation, request_body)
1533
1624
  self.ctx.validate_required_body_fields(operation, params, action, entity)
1534
1625
 
1535
- metadata_response = await self.ctx.http_client.request(
1626
+ metadata_response, _ = await self.ctx.http_client.request(
1536
1627
  method=operation.method,
1537
1628
  path=path,
1538
1629
  params=query_params,
@@ -1547,7 +1638,7 @@ class _DownloadOperationHandler:
1547
1638
  )
1548
1639
 
1549
1640
  # Step 3: Stream file from extracted URL
1550
- file_response = await self.ctx.http_client.request(
1641
+ file_response, _ = await self.ctx.http_client.request(
1551
1642
  method="GET",
1552
1643
  path=file_url,
1553
1644
  headers=headers,
@@ -1555,7 +1646,7 @@ class _DownloadOperationHandler:
1555
1646
  )
1556
1647
  else:
1557
1648
  # One-step direct download: stream file directly from endpoint
1558
- file_response = await self.ctx.http_client.request(
1649
+ file_response, _ = await self.ctx.http_client.request(
1559
1650
  method=operation.method,
1560
1651
  path=path,
1561
1652
  params=query_params,
@@ -421,10 +421,14 @@ class HTTPClient:
421
421
  headers: dict[str, str] | None = None,
422
422
  *,
423
423
  stream: bool = False,
424
- ):
424
+ ) -> tuple[dict[str, Any], dict[str, str]]:
425
425
  """Execute a single HTTP request attempt (no retries).
426
426
 
427
427
  This is the core request logic, separated from retry handling.
428
+
429
+ Returns:
430
+ Tuple of (response_data, response_headers) for non-streaming requests.
431
+ For streaming requests, returns (response_object, response_headers).
428
432
  """
429
433
  # Ensure auth credentials are initialized (proactive refresh if needed)
430
434
  await self._ensure_auth_initialized()
@@ -474,8 +478,9 @@ class HTTPClient:
474
478
  request_id=request_id,
475
479
  status_code=status_code,
476
480
  response_body=f"<binary content, {response.headers.get('content-length', 'unknown')} bytes>",
481
+ response_headers=dict(response.headers),
477
482
  )
478
- return response
483
+ return response, dict(response.headers)
479
484
 
480
485
  # Parse response - handle non-JSON responses gracefully
481
486
  content_type = response.headers.get("content-type", "")
@@ -500,8 +505,9 @@ class HTTPClient:
500
505
  request_id=request_id,
501
506
  status_code=status_code,
502
507
  response_body=response_data,
508
+ response_headers=dict(response.headers),
503
509
  )
504
- return response_data
510
+ return response_data, dict(response.headers)
505
511
 
506
512
  except AuthenticationError as e:
507
513
  # Auth error (401, 403) - handle token refresh
@@ -631,7 +637,7 @@ class HTTPClient:
631
637
  *,
632
638
  stream: bool = False,
633
639
  _auth_retry_attempted: bool = False,
634
- ):
640
+ ) -> tuple[dict[str, Any], dict[str, str]]:
635
641
  """Make an async HTTP request with optional streaming and automatic retries.
636
642
 
637
643
  Args:
@@ -644,8 +650,9 @@ class HTTPClient:
644
650
  stream: If True, do not eagerly read the body (useful for downloads)
645
651
 
646
652
  Returns:
647
- - If stream=False: Parsed JSON (dict) or empty dict
648
- - If stream=True: Response object suitable for streaming
653
+ Tuple of (response_data, response_headers):
654
+ - If stream=False: (parsed JSON dict or empty dict, response headers dict)
655
+ - If stream=True: (response object suitable for streaming, response headers dict)
649
656
 
650
657
  Raises:
651
658
  HTTPStatusError: If request fails with 4xx/5xx status after all retries
@@ -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:
@@ -43,6 +43,7 @@ from .models import (
43
43
  _F = TypeVar("_F", bound=Callable[..., Any])
44
44
 
45
45
 
46
+
46
47
  class SlackConnector:
47
48
  """
48
49
  Type-safe Slack API connector.
@@ -176,14 +176,6 @@ class ChannelResponse(BaseModel):
176
176
  ok: Union[bool, Any] = Field(default=None)
177
177
  channel: Union[Channel, Any] = Field(default=None)
178
178
 
179
- class Reaction(BaseModel):
180
- """Message reaction"""
181
- model_config = ConfigDict(extra="allow", populate_by_name=True)
182
-
183
- name: Union[str | None, Any] = Field(default=None)
184
- users: Union[list[str] | None, Any] = Field(default=None)
185
- count: Union[int | None, Any] = Field(default=None)
186
-
187
179
  class Attachment(BaseModel):
188
180
  """Message attachment"""
189
181
  model_config = ConfigDict(extra="allow", populate_by_name=True)
@@ -229,6 +221,14 @@ class File(BaseModel):
229
221
  created: Union[int | None, Any] = Field(default=None)
230
222
  timestamp: Union[int | None, Any] = Field(default=None)
231
223
 
224
+ class Reaction(BaseModel):
225
+ """Message reaction"""
226
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
227
+
228
+ name: Union[str | None, Any] = Field(default=None)
229
+ users: Union[list[str] | None, Any] = Field(default=None)
230
+ count: Union[int | None, Any] = Field(default=None)
231
+
232
232
  class Message(BaseModel):
233
233
  """Slack message object"""
234
234
  model_config = ConfigDict(extra="allow", populate_by_name=True)
@@ -375,6 +375,7 @@ class SlackExecuteResultWithMeta(SlackExecuteResult[T], Generic[T, S]):
375
375
  """Metadata about the response (e.g., pagination cursors, record counts)."""
376
376
 
377
377
 
378
+
378
379
  # ===== OPERATION RESULT TYPE ALIASES =====
379
380
 
380
381
  # Concrete type aliases for each operation result.
@@ -54,3 +54,4 @@ class ThreadsListParams(TypedDict):
54
54
  oldest: NotRequired[str]
55
55
  latest: NotRequired[str]
56
56
  inclusive: NotRequired[bool]
57
+
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "airbyte-agent-slack"
3
- version = "0.1.2"
3
+ version = "0.1.7"
4
4
  description = "Airbyte Slack Connector for AI platforms"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -1,16 +0,0 @@
1
- # Slack changelog
2
-
3
- ## [0.1.2] - 2026-01-15
4
- - Updated connector definition (YAML version 0.1.1)
5
- - Source commit: 61a2e822
6
- - SDK version: 0.1.0
7
-
8
- ## [0.1.1] - 2026-01-15
9
- - Updated connector definition (YAML version 0.1.1)
10
- - Source commit: 35211193
11
- - SDK version: 0.1.0
12
-
13
- ## [0.1.0] - 2026-01-15
14
- - Updated connector definition (YAML version 0.1.1)
15
- - Source commit: 8b64ece5
16
- - SDK version: 0.1.0