airbyte-agent-mcp 0.1.33__py3-none-any.whl → 0.1.60__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.
Files changed (31) hide show
  1. airbyte_agent_mcp/_vendored/connector_sdk/auth_strategies.py +2 -5
  2. airbyte_agent_mcp/_vendored/connector_sdk/auth_template.py +1 -1
  3. airbyte_agent_mcp/_vendored/connector_sdk/cloud_utils/client.py +26 -26
  4. airbyte_agent_mcp/_vendored/connector_sdk/connector_model_loader.py +11 -4
  5. airbyte_agent_mcp/_vendored/connector_sdk/constants.py +1 -1
  6. airbyte_agent_mcp/_vendored/connector_sdk/executor/hosted_executor.py +10 -11
  7. airbyte_agent_mcp/_vendored/connector_sdk/executor/local_executor.py +163 -34
  8. airbyte_agent_mcp/_vendored/connector_sdk/extensions.py +43 -5
  9. airbyte_agent_mcp/_vendored/connector_sdk/http/response.py +2 -0
  10. airbyte_agent_mcp/_vendored/connector_sdk/http_client.py +50 -43
  11. airbyte_agent_mcp/_vendored/connector_sdk/introspection.py +262 -0
  12. airbyte_agent_mcp/_vendored/connector_sdk/logging/logger.py +9 -9
  13. airbyte_agent_mcp/_vendored/connector_sdk/logging/types.py +10 -10
  14. airbyte_agent_mcp/_vendored/connector_sdk/observability/config.py +179 -0
  15. airbyte_agent_mcp/_vendored/connector_sdk/observability/models.py +6 -6
  16. airbyte_agent_mcp/_vendored/connector_sdk/observability/session.py +41 -32
  17. airbyte_agent_mcp/_vendored/connector_sdk/performance/metrics.py +3 -3
  18. airbyte_agent_mcp/_vendored/connector_sdk/schema/base.py +20 -18
  19. airbyte_agent_mcp/_vendored/connector_sdk/schema/components.py +59 -58
  20. airbyte_agent_mcp/_vendored/connector_sdk/schema/connector.py +22 -33
  21. airbyte_agent_mcp/_vendored/connector_sdk/schema/extensions.py +102 -9
  22. airbyte_agent_mcp/_vendored/connector_sdk/schema/operations.py +32 -32
  23. airbyte_agent_mcp/_vendored/connector_sdk/schema/security.py +44 -34
  24. airbyte_agent_mcp/_vendored/connector_sdk/secrets.py +2 -2
  25. airbyte_agent_mcp/_vendored/connector_sdk/telemetry/events.py +9 -8
  26. airbyte_agent_mcp/_vendored/connector_sdk/telemetry/tracker.py +9 -5
  27. airbyte_agent_mcp/_vendored/connector_sdk/types.py +7 -3
  28. airbyte_agent_mcp/server.py +34 -1
  29. {airbyte_agent_mcp-0.1.33.dist-info → airbyte_agent_mcp-0.1.60.dist-info}/METADATA +1 -1
  30. {airbyte_agent_mcp-0.1.33.dist-info → airbyte_agent_mcp-0.1.60.dist-info}/RECORD +31 -29
  31. {airbyte_agent_mcp-0.1.33.dist-info → airbyte_agent_mcp-0.1.60.dist-info}/WHEEL +0 -0
@@ -7,6 +7,7 @@ References:
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ from collections.abc import Iterator
10
11
  from typing import Any
11
12
 
12
13
  from pydantic import BaseModel, ConfigDict, Field, field_validator
@@ -15,7 +16,7 @@ from ..constants import OPENAPI_VERSION_PREFIX
15
16
 
16
17
  from .base import Info, Server
17
18
  from .components import Components
18
- from .operations import PathItem
19
+ from .operations import Operation, PathItem
19
20
  from .security import SecurityRequirement
20
21
 
21
22
 
@@ -33,6 +34,10 @@ class Tag(BaseModel):
33
34
  external_docs: dict[str, Any] | None = Field(None, alias="externalDocs")
34
35
 
35
36
 
37
+ # HTTP methods supported by OpenAPI operations
38
+ HTTP_METHODS = frozenset({"get", "post", "put", "patch", "delete", "options", "head", "trace"})
39
+
40
+
36
41
  class ExternalDocs(BaseModel):
37
42
  """
38
43
  External documentation reference.
@@ -79,7 +84,7 @@ class OpenAPIConnector(BaseModel):
79
84
  raise ValueError(f"OpenAPI version must be {OPENAPI_VERSION_PREFIX}x, got: {v}")
80
85
  return v
81
86
 
82
- def get_entity_operations(self, entity_name: str) -> list[tuple[str, str, Any]]:
87
+ def get_entity_operations(self, entity_name: str) -> list[tuple[str, str, Operation]]:
83
88
  """
84
89
  Get all operations for a specific entity.
85
90
 
@@ -89,22 +94,7 @@ class OpenAPIConnector(BaseModel):
89
94
  Returns:
90
95
  List of tuples: (path, method, operation)
91
96
  """
92
- results = []
93
- for path, path_item in self.paths.items():
94
- for method in [
95
- "get",
96
- "post",
97
- "put",
98
- "patch",
99
- "delete",
100
- "options",
101
- "head",
102
- "trace",
103
- ]:
104
- operation = getattr(path_item, method, None)
105
- if operation and operation.x_airbyte_entity == entity_name:
106
- results.append((path, method, operation))
107
- return results
97
+ return [(path, method, op) for path, method, op in self._iter_operations() if op.x_airbyte_entity == entity_name]
108
98
 
109
99
  def list_entities(self) -> list[str]:
110
100
  """
@@ -113,19 +103,18 @@ class OpenAPIConnector(BaseModel):
113
103
  Returns:
114
104
  Sorted list of unique entity names
115
105
  """
116
- entities = set()
117
- for path_item in self.paths.values():
118
- for method in [
119
- "get",
120
- "post",
121
- "put",
122
- "patch",
123
- "delete",
124
- "options",
125
- "head",
126
- "trace",
127
- ]:
128
- operation = getattr(path_item, method, None)
129
- if operation and operation.x_airbyte_entity:
130
- entities.add(operation.x_airbyte_entity)
106
+ entities = {op.x_airbyte_entity for _, _, op in self._iter_operations() if op.x_airbyte_entity}
131
107
  return sorted(entities)
108
+
109
+ def _iter_operations(self) -> Iterator[tuple[str, str, Operation]]:
110
+ """
111
+ Iterate over all operations in the spec.
112
+
113
+ Yields:
114
+ Tuples of (path, method, operation) for each defined operation
115
+ """
116
+ for path, path_item in self.paths.items():
117
+ for method in HTTP_METHODS:
118
+ operation = getattr(path_item, method, None)
119
+ if operation:
120
+ yield path, method, operation
@@ -14,7 +14,7 @@ are implemented.
14
14
 
15
15
  from typing import Literal, Optional
16
16
 
17
- from pydantic import BaseModel, ConfigDict
17
+ from pydantic import BaseModel, ConfigDict, Field
18
18
 
19
19
 
20
20
  class PaginationConfig(BaseModel):
@@ -33,22 +33,22 @@ class PaginationConfig(BaseModel):
33
33
  limit_param: str = "limit"
34
34
 
35
35
  # Cursor-based pagination
36
- cursor_param: Optional[str] = None
37
- cursor_source: Optional[Literal["body", "headers"]] = "body"
38
- cursor_path: Optional[str] = None
36
+ cursor_param: str | None = None
37
+ cursor_source: Literal["body", "headers"] | None = "body"
38
+ cursor_path: str | None = None
39
39
 
40
40
  # Offset-based pagination
41
- offset_param: Optional[str] = None
41
+ offset_param: str | None = None
42
42
 
43
43
  # Page-based pagination
44
- page_param: Optional[str] = None
44
+ page_param: str | None = None
45
45
 
46
46
  # Response parsing
47
47
  data_path: str = "data"
48
- has_more_path: Optional[str] = None
48
+ has_more_path: str | None = None
49
49
 
50
50
  # Limits
51
- max_page_size: Optional[int] = None
51
+ max_page_size: int | None = None
52
52
  default_page_size: int = 100
53
53
 
54
54
 
@@ -66,7 +66,7 @@ class RateLimitConfig(BaseModel):
66
66
 
67
67
  max_requests: int
68
68
  time_window_seconds: int
69
- retry_after_header: Optional[str] = "Retry-After"
69
+ retry_after_header: str | None = "Retry-After"
70
70
  respect_retry_after: bool = True
71
71
 
72
72
 
@@ -107,3 +107,96 @@ class RetryConfig(BaseModel):
107
107
  # Header-based delay extraction
108
108
  retry_after_header: str = "Retry-After"
109
109
  retry_after_format: Literal["seconds", "milliseconds", "unix_timestamp"] = "seconds"
110
+
111
+
112
+ class CacheFieldConfig(BaseModel):
113
+ """
114
+ Field configuration for cache mapping.
115
+
116
+ Defines a single field in a cache entity, with optional name aliasing
117
+ to map between user-facing field names and cache storage names.
118
+
119
+ Used in x-airbyte-cache extension for api_search operations.
120
+ """
121
+
122
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
123
+
124
+ name: str
125
+ x_airbyte_name: Optional[str] = Field(default=None, alias="x-airbyte-name")
126
+ type: str | list[str]
127
+ description: str
128
+
129
+ @property
130
+ def cache_name(self) -> str:
131
+ """Return cache name, falling back to name if alias not specified."""
132
+ return self.x_airbyte_name or self.name
133
+
134
+
135
+ class CacheEntityConfig(BaseModel):
136
+ """
137
+ Entity configuration for cache mapping.
138
+
139
+ Defines a cache-enabled entity with its fields and optional name aliasing
140
+ to map between user-facing entity names and cache storage names.
141
+
142
+ Used in x-airbyte-cache extension for api_search operations.
143
+ """
144
+
145
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
146
+
147
+ entity: str
148
+ x_airbyte_name: Optional[str] = Field(default=None, alias="x-airbyte-name")
149
+ fields: list[CacheFieldConfig]
150
+
151
+ @property
152
+ def cache_name(self) -> str:
153
+ """Return cache entity name, falling back to entity if alias not specified."""
154
+ return self.x_airbyte_name or self.entity
155
+
156
+
157
+ class CacheConfig(BaseModel):
158
+ """
159
+ Cache configuration extension (x-airbyte-cache).
160
+
161
+ Defines cache-enabled entities and their field mappings for api_search operations.
162
+ Supports optional name aliasing via x-airbyte-name for both entities and fields,
163
+ enabling bidirectional mapping between user-facing names and cache storage names.
164
+
165
+ This extension is added to the Info model and provides field-level mapping for
166
+ search operations that use cached data.
167
+
168
+ Example YAML usage:
169
+ info:
170
+ title: Stripe API
171
+ x-airbyte-cache:
172
+ entities:
173
+ - entity: customers
174
+ stream: customers
175
+ fields:
176
+ - name: email
177
+ type: ["null", "string"]
178
+ description: "Customer email address"
179
+ - name: customer_name
180
+ x-airbyte-name: name
181
+ type: ["null", "string"]
182
+ description: "Customer full name"
183
+ """
184
+
185
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
186
+
187
+ entities: list[CacheEntityConfig]
188
+
189
+ def get_entity_mapping(self, user_entity: str) -> Optional[CacheEntityConfig]:
190
+ """
191
+ Get entity config by user-facing name.
192
+
193
+ Args:
194
+ user_entity: User-facing entity name to look up
195
+
196
+ Returns:
197
+ CacheEntityConfig if found, None otherwise
198
+ """
199
+ for entity in self.entities:
200
+ if entity.entity == user_entity:
201
+ return entity
202
+ return None
@@ -6,7 +6,7 @@ References:
6
6
  - https://spec.openapis.org/oas/v3.1.0#path-item-object
7
7
  """
8
8
 
9
- from typing import Any, Dict, List, Optional
9
+ from typing import Any, Dict, List
10
10
 
11
11
  from pydantic import BaseModel, ConfigDict, Field, model_validator
12
12
 
@@ -34,38 +34,38 @@ class Operation(BaseModel):
34
34
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
35
35
 
36
36
  # Standard OpenAPI fields
37
- tags: Optional[List[str]] = None
38
- summary: Optional[str] = None
39
- description: Optional[str] = None
40
- external_docs: Optional[Dict[str, Any]] = Field(None, alias="externalDocs")
41
- operation_id: Optional[str] = Field(None, alias="operationId")
42
- parameters: Optional[List[Parameter]] = None
43
- request_body: Optional[RequestBody] = Field(None, alias="requestBody")
37
+ tags: List[str] | None = None
38
+ summary: str | None = None
39
+ description: str | None = None
40
+ external_docs: Dict[str, Any] | None = Field(None, alias="externalDocs")
41
+ operation_id: str | None = Field(None, alias="operationId")
42
+ parameters: List[Parameter] | None = None
43
+ request_body: RequestBody | None = Field(None, alias="requestBody")
44
44
  responses: Dict[str, Response] = Field(default_factory=dict)
45
- callbacks: Optional[Dict[str, Any]] = None
46
- deprecated: Optional[bool] = None
47
- security: Optional[List[SecurityRequirement]] = None
48
- servers: Optional[List[Any]] = None # Can override root servers
45
+ callbacks: Dict[str, Any] | None = None
46
+ deprecated: bool | None = None
47
+ security: List[SecurityRequirement] | None = None
48
+ servers: List[Any] | None = None # Can override root servers
49
49
 
50
50
  # Airbyte extensions
51
51
  x_airbyte_entity: str = Field(..., alias="x-airbyte-entity")
52
52
  x_airbyte_action: ActionTypeLiteral = Field(..., alias="x-airbyte-action")
53
- x_airbyte_path_override: Optional[PathOverrideConfig] = Field(
53
+ x_airbyte_path_override: PathOverrideConfig | None = Field(
54
54
  None,
55
55
  alias="x-airbyte-path-override",
56
- description=("Override path for HTTP requests when OpenAPI path " "differs from actual endpoint"),
56
+ description=("Override path for HTTP requests when OpenAPI path differs from actual endpoint"),
57
57
  )
58
- x_airbyte_record_extractor: Optional[str] = Field(
58
+ x_airbyte_record_extractor: str | None = Field(
59
59
  None,
60
60
  alias="x-airbyte-record-extractor",
61
61
  description=(
62
62
  "JSONPath expression to extract records from API response envelopes. "
63
63
  "When specified, executor extracts data at this path instead of returning "
64
- "full response. Returns array for list/search actions, single record for "
64
+ "full response. Returns array for list/api_search actions, single record for "
65
65
  "get/create/update/delete actions."
66
66
  ),
67
67
  )
68
- x_airbyte_meta_extractor: Optional[Dict[str, str]] = Field(
68
+ x_airbyte_meta_extractor: Dict[str, str] | None = Field(
69
69
  None,
70
70
  alias="x-airbyte-meta-extractor",
71
71
  description=(
@@ -76,8 +76,8 @@ class Operation(BaseModel):
76
76
  "Example: {'pagination': '$.pagination', 'request_id': '$.requestId'}"
77
77
  ),
78
78
  )
79
- x_airbyte_file_url: Optional[str] = Field(None, alias="x-airbyte-file-url")
80
- x_airbyte_untested: Optional[bool] = Field(
79
+ x_airbyte_file_url: str | None = Field(None, alias="x-airbyte-file-url")
80
+ x_airbyte_untested: bool | None = Field(
81
81
  None,
82
82
  alias="x-airbyte-untested",
83
83
  description=(
@@ -127,20 +127,20 @@ class PathItem(BaseModel):
127
127
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
128
128
 
129
129
  # Common fields for all operations
130
- summary: Optional[str] = None
131
- description: Optional[str] = None
132
- servers: Optional[List[Any]] = None
133
- parameters: Optional[List[Parameter]] = None
130
+ summary: str | None = None
131
+ description: str | None = None
132
+ servers: List[Any] | None = None
133
+ parameters: List[Parameter] | None = None
134
134
 
135
135
  # HTTP methods (all optional)
136
- get: Optional[Operation] = None
137
- put: Optional[Operation] = None
138
- post: Optional[Operation] = None
139
- delete: Optional[Operation] = None
140
- options: Optional[Operation] = None
141
- head: Optional[Operation] = None
142
- patch: Optional[Operation] = None
143
- trace: Optional[Operation] = None
136
+ get: Operation | None = None
137
+ put: Operation | None = None
138
+ post: Operation | None = None
139
+ delete: Operation | None = None
140
+ options: Operation | None = None
141
+ head: Operation | None = None
142
+ patch: Operation | None = None
143
+ trace: Operation | None = None
144
144
 
145
145
  # Reference support
146
- ref: Optional[str] = Field(None, alias="$ref")
146
+ ref: str | None = Field(None, alias="$ref")
@@ -6,7 +6,7 @@ References:
6
6
  - https://spec.openapis.org/oas/v3.1.0#oauth-flows-object
7
7
  """
8
8
 
9
- from typing import Any, Dict, List, Literal, Optional
9
+ from typing import Any, Dict, List, Literal
10
10
 
11
11
  from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
12
12
 
@@ -20,9 +20,9 @@ class OAuth2Flow(BaseModel):
20
20
 
21
21
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
22
22
 
23
- authorization_url: Optional[str] = Field(None, alias="authorizationUrl")
24
- token_url: Optional[str] = Field(None, alias="tokenUrl")
25
- refresh_url: Optional[str] = Field(None, alias="refreshUrl")
23
+ authorization_url: str | None = Field(None, alias="authorizationUrl")
24
+ token_url: str | None = Field(None, alias="tokenUrl")
25
+ refresh_url: str | None = Field(None, alias="refreshUrl")
26
26
  scopes: Dict[str, str] = Field(default_factory=dict)
27
27
 
28
28
 
@@ -35,10 +35,10 @@ class OAuth2Flows(BaseModel):
35
35
 
36
36
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
37
37
 
38
- implicit: Optional[OAuth2Flow] = None
39
- password: Optional[OAuth2Flow] = None
40
- client_credentials: Optional[OAuth2Flow] = Field(None, alias="clientCredentials")
41
- authorization_code: Optional[OAuth2Flow] = Field(None, alias="authorizationCode")
38
+ implicit: OAuth2Flow | None = None
39
+ password: OAuth2Flow | None = None
40
+ client_credentials: OAuth2Flow | None = Field(None, alias="clientCredentials")
41
+ authorization_code: OAuth2Flow | None = Field(None, alias="authorizationCode")
42
42
 
43
43
 
44
44
  class AuthConfigFieldSpec(BaseModel):
@@ -51,12 +51,12 @@ class AuthConfigFieldSpec(BaseModel):
51
51
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
52
52
 
53
53
  type: Literal["string", "integer", "boolean", "number"] = "string"
54
- title: Optional[str] = None
55
- description: Optional[str] = None
56
- format: Optional[str] = None # e.g., "email", "uri"
57
- pattern: Optional[str] = None # Regex validation
54
+ title: str | None = None
55
+ description: str | None = None
56
+ format: str | None = None # e.g., "email", "uri"
57
+ pattern: str | None = None # Regex validation
58
58
  airbyte_secret: bool = Field(False, alias="airbyte_secret")
59
- default: Optional[Any] = None
59
+ default: Any | None = None
60
60
 
61
61
 
62
62
  class AuthConfigOption(BaseModel):
@@ -68,8 +68,8 @@ class AuthConfigOption(BaseModel):
68
68
 
69
69
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
70
70
 
71
- title: Optional[str] = None
72
- description: Optional[str] = None
71
+ title: str | None = None
72
+ description: str | None = None
73
73
  type: Literal["object"] = "object"
74
74
  required: List[str] = Field(default_factory=list)
75
75
  properties: Dict[str, AuthConfigFieldSpec] = Field(default_factory=dict)
@@ -77,6 +77,10 @@ class AuthConfigOption(BaseModel):
77
77
  default_factory=dict,
78
78
  description="Mapping from auth parameters (e.g., 'username', 'password', 'token') to template strings using ${field} syntax",
79
79
  )
80
+ replication_auth_key_mapping: Dict[str, str] | None = Field(
81
+ None,
82
+ description="Mapping from source config paths (e.g., 'credentials.api_key') to auth config keys for direct connectors",
83
+ )
80
84
 
81
85
 
82
86
  class AirbyteAuthConfig(BaseModel):
@@ -92,15 +96,21 @@ class AirbyteAuthConfig(BaseModel):
92
96
  model_config = ConfigDict(populate_by_name=True, extra="forbid")
93
97
 
94
98
  # Single option fields
95
- title: Optional[str] = None
96
- description: Optional[str] = None
97
- type: Optional[Literal["object"]] = None
98
- required: Optional[List[str]] = None
99
- properties: Optional[Dict[str, AuthConfigFieldSpec]] = None
100
- auth_mapping: Optional[Dict[str, str]] = None
99
+ title: str | None = None
100
+ description: str | None = None
101
+ type: Literal["object"] | None = None
102
+ required: List[str] | None = None
103
+ properties: Dict[str, AuthConfigFieldSpec] | None = None
104
+ auth_mapping: Dict[str, str] | None = None
105
+
106
+ # Replication connector auth mapping
107
+ replication_auth_key_mapping: Dict[str, str] | None = Field(
108
+ None,
109
+ description="Mapping from source config paths (e.g., 'credentials.api_key') to auth config keys for direct connectors",
110
+ )
101
111
 
102
112
  # Multiple options (oneOf)
103
- one_of: Optional[List[AuthConfigOption]] = Field(None, alias="oneOf")
113
+ one_of: List[AuthConfigOption] | None = Field(None, alias="oneOf")
104
114
 
105
115
  @model_validator(mode="after")
106
116
  def validate_config_structure(self) -> "AirbyteAuthConfig":
@@ -151,27 +161,27 @@ class SecurityScheme(BaseModel):
151
161
 
152
162
  # Standard OpenAPI fields
153
163
  type: Literal["apiKey", "http", "oauth2", "openIdConnect"]
154
- description: Optional[str] = None
164
+ description: str | None = None
155
165
 
156
166
  # apiKey specific
157
- name: Optional[str] = None
158
- in_: Optional[Literal["query", "header", "cookie"]] = Field(None, alias="in")
167
+ name: str | None = None
168
+ in_: Literal["query", "header", "cookie"] | None = Field(None, alias="in")
159
169
 
160
170
  # http specific
161
- scheme: Optional[str] = None # e.g., "basic", "bearer", "digest"
162
- bearer_format: Optional[str] = Field(None, alias="bearerFormat")
171
+ scheme: str | None = None # e.g., "basic", "bearer", "digest"
172
+ bearer_format: str | None = Field(None, alias="bearerFormat")
163
173
 
164
174
  # oauth2 specific
165
- flows: Optional[OAuth2Flows] = None
175
+ flows: OAuth2Flows | None = None
166
176
 
167
177
  # openIdConnect specific
168
- open_id_connect_url: Optional[str] = Field(None, alias="openIdConnectUrl")
178
+ open_id_connect_url: str | None = Field(None, alias="openIdConnectUrl")
169
179
 
170
180
  # Airbyte extensions
171
- x_token_path: Optional[str] = Field(None, alias="x-airbyte-token-path")
172
- x_token_refresh: Optional[Dict[str, Any]] = Field(None, alias="x-airbyte-token-refresh")
173
- x_airbyte_auth_config: Optional[AirbyteAuthConfig] = Field(None, alias="x-airbyte-auth-config")
174
- x_airbyte_token_extract: Optional[List[str]] = Field(
181
+ x_token_path: str | None = Field(None, alias="x-airbyte-token-path")
182
+ x_token_refresh: Dict[str, Any] | None = Field(None, alias="x-airbyte-token-refresh")
183
+ x_airbyte_auth_config: AirbyteAuthConfig | None = Field(None, alias="x-airbyte-auth-config")
184
+ x_airbyte_token_extract: List[str] | None = Field(
175
185
  None,
176
186
  alias="x-airbyte-token-extract",
177
187
  description="List of fields to extract from OAuth2 token responses and use as server variables",
@@ -179,7 +189,7 @@ class SecurityScheme(BaseModel):
179
189
 
180
190
  @field_validator("x_airbyte_token_extract", mode="after")
181
191
  @classmethod
182
- def validate_token_extract(cls, v: Optional[List[str]]) -> Optional[List[str]]:
192
+ def validate_token_extract(cls, v: List[str] | None) -> List[str] | None:
183
193
  """Validate x-airbyte-token-extract has no duplicates."""
184
194
  if v is not None:
185
195
  if len(v) != len(set(v)):
@@ -14,7 +14,7 @@ Example:
14
14
 
15
15
  import os
16
16
  import re
17
- from typing import Any, Dict, Optional
17
+ from typing import Any, Dict
18
18
 
19
19
  from pydantic import SecretStr
20
20
 
@@ -72,7 +72,7 @@ class SecretResolutionError(Exception):
72
72
  def resolve_env_var_references(
73
73
  secret_mappings: Dict[str, Any],
74
74
  strict: bool = True,
75
- env_vars: Optional[Dict[str, str]] = None,
75
+ env_vars: Dict[str, str] | None = None,
76
76
  ) -> Dict[str, str]:
77
77
  """Resolve environment variable references in secret values.
78
78
 
@@ -1,8 +1,8 @@
1
1
  """Telemetry event models."""
2
2
 
3
- from dataclasses import asdict, dataclass
3
+ from dataclasses import asdict, dataclass, field
4
4
  from datetime import datetime
5
- from typing import Any, Dict, Optional
5
+ from typing import Any, Dict
6
6
 
7
7
 
8
8
  @dataclass
@@ -13,6 +13,7 @@ class BaseEvent:
13
13
  session_id: str
14
14
  user_id: str
15
15
  execution_context: str
16
+ is_internal_user: bool = field(default=False, kw_only=True)
16
17
 
17
18
  def to_dict(self) -> Dict[str, Any]:
18
19
  """Convert event to dictionary with ISO formatted timestamp."""
@@ -29,8 +30,8 @@ class ConnectorInitEvent(BaseEvent):
29
30
  python_version: str
30
31
  os_name: str
31
32
  os_version: str
32
- public_ip: Optional[str] = None
33
- connector_version: Optional[str] = None
33
+ public_ip: str | None = None
34
+ connector_version: str | None = None
34
35
 
35
36
 
36
37
  @dataclass
@@ -41,9 +42,9 @@ class OperationEvent(BaseEvent):
41
42
  entity: str
42
43
  action: str
43
44
  timing_ms: float
44
- public_ip: Optional[str] = None
45
- status_code: Optional[int] = None
46
- error_type: Optional[str] = None
45
+ public_ip: str | None = None
46
+ status_code: int | None = None
47
+ error_type: str | None = None
47
48
 
48
49
 
49
50
  @dataclass
@@ -55,4 +56,4 @@ class SessionEndEvent(BaseEvent):
55
56
  operation_count: int
56
57
  success_count: int
57
58
  failure_count: int
58
- public_ip: Optional[str] = None
59
+ public_ip: str | None = None
@@ -4,7 +4,6 @@ import logging
4
4
  import platform
5
5
  import sys
6
6
  from datetime import datetime
7
- from typing import Optional
8
7
 
9
8
  from ..observability import ObservabilitySession
10
9
 
@@ -20,7 +19,7 @@ class SegmentTracker:
20
19
  def __init__(
21
20
  self,
22
21
  session: ObservabilitySession,
23
- mode: Optional[TelemetryMode] = None,
22
+ mode: TelemetryMode | None = None,
24
23
  ):
25
24
  self.session = session
26
25
  self.mode = mode or TelemetryConfig.get_mode()
@@ -31,6 +30,8 @@ class SegmentTracker:
31
30
 
32
31
  if self.enabled:
33
32
  try:
33
+ # NOTE: Import here intentionally - segment is an optional dependency.
34
+ # This allows the SDK to work without telemetry if segment is not installed.
34
35
  import segment.analytics as analytics
35
36
 
36
37
  analytics.write_key = SEGMENT_WRITE_KEY
@@ -47,7 +48,7 @@ class SegmentTracker:
47
48
 
48
49
  def track_connector_init(
49
50
  self,
50
- connector_version: Optional[str] = None,
51
+ connector_version: str | None = None,
51
52
  ) -> None:
52
53
  """Track connector initialization."""
53
54
  if not self.enabled or not self._analytics:
@@ -59,6 +60,7 @@ class SegmentTracker:
59
60
  session_id=self.session.session_id,
60
61
  user_id=self.session.user_id,
61
62
  execution_context=self.session.execution_context,
63
+ is_internal_user=self.session.is_internal_user,
62
64
  public_ip=self.session.public_ip,
63
65
  connector_name=self.session.connector_name,
64
66
  connector_version=connector_version,
@@ -81,9 +83,9 @@ class SegmentTracker:
81
83
  self,
82
84
  entity: str,
83
85
  action: str,
84
- status_code: Optional[int],
86
+ status_code: int | None,
85
87
  timing_ms: float,
86
- error_type: Optional[str] = None,
88
+ error_type: str | None = None,
87
89
  ) -> None:
88
90
  """Track API operation."""
89
91
  # Always track success/failure counts (useful even when tracking is disabled)
@@ -101,6 +103,7 @@ class SegmentTracker:
101
103
  session_id=self.session.session_id,
102
104
  user_id=self.session.user_id,
103
105
  execution_context=self.session.execution_context,
106
+ is_internal_user=self.session.is_internal_user,
104
107
  public_ip=self.session.public_ip,
105
108
  connector_name=self.session.connector_name,
106
109
  entity=entity,
@@ -130,6 +133,7 @@ class SegmentTracker:
130
133
  session_id=self.session.session_id,
131
134
  user_id=self.session.user_id,
132
135
  execution_context=self.session.execution_context,
136
+ is_internal_user=self.session.is_internal_user,
133
137
  public_ip=self.session.public_ip,
134
138
  connector_name=self.session.connector_name,
135
139
  duration_seconds=self.session.duration_seconds(),
@@ -22,7 +22,7 @@ class Action(str, Enum):
22
22
  UPDATE = "update"
23
23
  DELETE = "delete"
24
24
  LIST = "list"
25
- SEARCH = "search"
25
+ API_SEARCH = "api_search"
26
26
  DOWNLOAD = "download"
27
27
  AUTHORIZE = "authorize"
28
28
 
@@ -140,7 +140,7 @@ class AuthConfig(BaseModel):
140
140
  ValueError: If this is a multi-auth config or invalid
141
141
  """
142
142
  if self.is_multi_auth():
143
- raise ValueError("Cannot call get_single_option() on multi-auth config. " "Use options list instead.")
143
+ raise ValueError("Cannot call get_single_option() on multi-auth config. Use options list instead.")
144
144
 
145
145
  if self.type is None:
146
146
  raise ValueError("Invalid AuthConfig: neither single-auth nor multi-auth")
@@ -161,7 +161,7 @@ class EndpointDefinition(BaseModel):
161
161
  path: str # e.g., /v1/customers/{id} (OpenAPI path)
162
162
  path_override: PathOverrideConfig | None = Field(
163
163
  None,
164
- description=("Path override config from x-airbyte-path-override. " "When set, overrides the path for actual HTTP requests."),
164
+ description=("Path override config from x-airbyte-path-override. When set, overrides the path for actual HTTP requests."),
165
165
  )
166
166
  action: Action | None = None # Semantic action (get, list, create, update, delete)
167
167
  description: str | None = None
@@ -221,6 +221,10 @@ class EntityDefinition(BaseModel):
221
221
  model_config = {"populate_by_name": True}
222
222
 
223
223
  name: str
224
+ stream_name: str | None = Field(
225
+ default=None,
226
+ description="Airbyte stream name for cache lookup (from x-airbyte-stream-name schema extension)",
227
+ )
224
228
  actions: list[Action]
225
229
  endpoints: dict[Action, EndpointDefinition]
226
230
  entity_schema: dict[str, Any] | None = Field(default=None, alias="schema")