airbyte-agent-zendesk-support 0.18.47__py3-none-any.whl → 0.18.57__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.
@@ -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
 
@@ -7,7 +7,7 @@ References:
7
7
  """
8
8
 
9
9
  from enum import StrEnum
10
- from typing import Dict
10
+ from typing import Any, Dict
11
11
  from uuid import UUID
12
12
 
13
13
  from pydantic import BaseModel, ConfigDict, Field, field_validator
@@ -152,7 +152,12 @@ class Server(BaseModel):
152
152
  url: str
153
153
  description: str | None = None
154
154
  variables: Dict[str, ServerVariable] = Field(default_factory=dict)
155
- x_airbyte_replication_user_config_mapping: Dict[str, str] | None = Field(default=None, alias="x-airbyte-replication-user-config-mapping")
155
+ x_airbyte_replication_environment_mapping: Dict[str, str] | None = Field(default=None, alias="x-airbyte-replication-environment-mapping")
156
+ x_airbyte_replication_environment_constants: Dict[str, Any] | None = Field(
157
+ default=None,
158
+ alias="x-airbyte-replication-environment-constants",
159
+ description="Constant values to always inject at environment config paths (e.g., 'region': 'us-east-1')",
160
+ )
156
161
 
157
162
  @field_validator("url")
158
163
  @classmethod
@@ -109,6 +109,20 @@ class AirbyteAuthConfig(BaseModel):
109
109
  description="Mapping from source config paths (e.g., 'credentials.api_key') to auth config keys for direct connectors",
110
110
  )
111
111
 
112
+ # Additional headers to inject alongside OAuth2 Bearer token
113
+ additional_headers: Dict[str, str] | None = Field(
114
+ None,
115
+ description=(
116
+ "Extra headers to inject with auth. Values support Jinja2 {{ variable }} template syntax "
117
+ "to reference secrets. Example: {'Amazon-Advertising-API-ClientId': '{{ client_id }}'}"
118
+ ),
119
+ )
120
+
121
+ # Replication connector auth constants
122
+ replication_auth_key_constants: Dict[str, Any] | None = Field(
123
+ None,
124
+ description="Constant values to always inject at source config paths (e.g., 'credentials.auth_type': 'OAuth2.0')",
125
+ )
112
126
  # Multiple options (oneOf)
113
127
  one_of: List[AuthConfigOption] | None = Field(None, alias="oneOf")
114
128
 
@@ -180,6 +180,15 @@ class EndpointDefinition(BaseModel):
180
180
  default_factory=dict,
181
181
  description="Schema for path params including defaults: {name: {type, default, required}}",
182
182
  )
183
+ header_params: list[str] = Field(default_factory=list) # Header parameters from OpenAPI
184
+ header_params_schema: dict[str, dict[str, Any]] = Field(
185
+ default_factory=dict,
186
+ description="Schema for header params including defaults: {name: {type, default, required}}",
187
+ )
188
+ request_body_defaults: dict[str, Any] = Field(
189
+ default_factory=dict,
190
+ description="Default values for request body fields from OpenAPI schema",
191
+ )
183
192
  content_type: ContentType = ContentType.JSON
184
193
  request_schema: dict[str, Any] | None = None
185
194
  response_schema: dict[str, Any] | None = None
@@ -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: