airbyte-agent-zendesk-chat 0.1.9__tar.gz → 0.1.12__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_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/AUTH.md +1 -1
  2. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/CHANGELOG.md +15 -0
  3. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/PKG-INFO +10 -6
  4. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/README.md +9 -5
  5. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/REFERENCE.md +32 -0
  6. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/__init__.py +4 -4
  7. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/connector_model_loader.py +138 -0
  8. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/executor/local_executor.py +3 -1
  9. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/introspection.py +222 -10
  10. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/schema/base.py +3 -1
  11. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/schema/components.py +5 -0
  12. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/schema/extensions.py +71 -0
  13. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/types.py +1 -0
  14. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/connector.py +94 -32
  15. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/connector_model.py +43 -1
  16. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/pyproject.toml +1 -1
  17. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/.gitignore +0 -0
  18. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/__init__.py +0 -0
  19. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/__init__.py +0 -0
  20. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/auth_strategies.py +0 -0
  21. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/auth_template.py +0 -0
  22. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/cloud_utils/__init__.py +0 -0
  23. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/cloud_utils/client.py +0 -0
  24. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/constants.py +0 -0
  25. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/exceptions.py +0 -0
  26. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/executor/__init__.py +0 -0
  27. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/executor/hosted_executor.py +0 -0
  28. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/executor/models.py +0 -0
  29. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/extensions.py +0 -0
  30. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/http/__init__.py +0 -0
  31. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/http/adapters/__init__.py +0 -0
  32. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/http/adapters/httpx_adapter.py +0 -0
  33. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/http/config.py +0 -0
  34. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/http/exceptions.py +0 -0
  35. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/http/protocols.py +0 -0
  36. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/http/response.py +0 -0
  37. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/http_client.py +0 -0
  38. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/logging/__init__.py +0 -0
  39. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/logging/logger.py +0 -0
  40. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/logging/types.py +0 -0
  41. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/observability/__init__.py +0 -0
  42. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/observability/config.py +0 -0
  43. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/observability/models.py +0 -0
  44. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/observability/redactor.py +0 -0
  45. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/observability/session.py +0 -0
  46. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/performance/__init__.py +0 -0
  47. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/performance/instrumentation.py +0 -0
  48. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/performance/metrics.py +0 -0
  49. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/schema/__init__.py +0 -0
  50. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/schema/connector.py +0 -0
  51. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/schema/operations.py +0 -0
  52. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/schema/security.py +0 -0
  53. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/secrets.py +0 -0
  54. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/telemetry/__init__.py +0 -0
  55. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/telemetry/config.py +0 -0
  56. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/telemetry/events.py +0 -0
  57. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/telemetry/tracker.py +0 -0
  58. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/utils.py +0 -0
  59. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/_vendored/connector_sdk/validation.py +0 -0
  60. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/models.py +28 -28
  61. {airbyte_agent_zendesk_chat-0.1.9 → airbyte_agent_zendesk_chat-0.1.12}/airbyte_agent_zendesk_chat/types.py +0 -0
@@ -69,7 +69,7 @@ connector = ZendeskChatConnector(
69
69
  )
70
70
 
71
71
  @agent.tool_plain # assumes you're using Pydantic AI
72
- @ZendeskChatConnector.describe
72
+ @ZendeskChatConnector.tool_utils
73
73
  async def zendesk-chat_execute(entity: str, action: str, params: dict | None = None):
74
74
  return await connector.execute(entity, action, params or {})
75
75
  ```
@@ -1,5 +1,20 @@
1
1
  # Zendesk Chat changelog
2
2
 
3
+ ## [0.1.12] - 2026-01-26
4
+ - Updated connector definition (YAML version 0.1.4)
5
+ - Source commit: b73c71e0
6
+ - SDK version: 0.1.0
7
+
8
+ ## [0.1.11] - 2026-01-24
9
+ - Updated connector definition (YAML version 0.1.4)
10
+ - Source commit: 609c1d86
11
+ - SDK version: 0.1.0
12
+
13
+ ## [0.1.10] - 2026-01-23
14
+ - Updated connector definition (YAML version 0.1.4)
15
+ - Source commit: 049f6ad5
16
+ - SDK version: 0.1.0
17
+
3
18
  ## [0.1.9] - 2026-01-23
4
19
  - Updated connector definition (YAML version 0.1.3)
5
20
  - Source commit: 416466da
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: airbyte-agent-zendesk-chat
3
- Version: 0.1.9
3
+ Version: 0.1.12
4
4
  Summary: Airbyte Zendesk-Chat 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/
@@ -104,7 +104,7 @@ connector = ZendeskChatConnector(
104
104
  )
105
105
 
106
106
  @agent.tool_plain # assumes you're using Pydantic AI
107
- @ZendeskChatConnector.describe
107
+ @ZendeskChatConnector.tool_utils
108
108
  async def zendesk-chat_execute(entity: str, action: str, params: dict | None = None):
109
109
  return await connector.execute(entity, action, params or {})
110
110
  ```
@@ -125,11 +125,15 @@ connector = ZendeskChatConnector(
125
125
  )
126
126
 
127
127
  @agent.tool_plain # assumes you're using Pydantic AI
128
- @ZendeskChatConnector.describe
128
+ @ZendeskChatConnector.tool_utils
129
129
  async def zendesk-chat_execute(entity: str, action: str, params: dict | None = None):
130
130
  return await connector.execute(entity, action, params or {})
131
131
  ```
132
132
 
133
+ ## Replication Configuration
134
+
135
+ This connector supports replication configuration for MULTI mode sources. See the [full reference documentation](./REFERENCE.md#replication-configuration) for details on available options like `start_date`.
136
+
133
137
  ## Full documentation
134
138
 
135
139
  This connector supports the following entities and actions.
@@ -158,6 +162,6 @@ For the service's official API docs, see the [Zendesk-Chat API reference](https:
158
162
 
159
163
  ## Version information
160
164
 
161
- - **Package version:** 0.1.9
162
- - **Connector version:** 0.1.3
163
- - **Generated with Connector SDK commit SHA:** 416466da4970ae5fd6c7f2c658a68e047e51efd9
165
+ - **Package version:** 0.1.12
166
+ - **Connector version:** 0.1.4
167
+ - **Generated with Connector SDK commit SHA:** b73c71e077aea8d316f7161dbd5014d4fb9f3098
@@ -71,7 +71,7 @@ connector = ZendeskChatConnector(
71
71
  )
72
72
 
73
73
  @agent.tool_plain # assumes you're using Pydantic AI
74
- @ZendeskChatConnector.describe
74
+ @ZendeskChatConnector.tool_utils
75
75
  async def zendesk-chat_execute(entity: str, action: str, params: dict | None = None):
76
76
  return await connector.execute(entity, action, params or {})
77
77
  ```
@@ -92,11 +92,15 @@ connector = ZendeskChatConnector(
92
92
  )
93
93
 
94
94
  @agent.tool_plain # assumes you're using Pydantic AI
95
- @ZendeskChatConnector.describe
95
+ @ZendeskChatConnector.tool_utils
96
96
  async def zendesk-chat_execute(entity: str, action: str, params: dict | None = None):
97
97
  return await connector.execute(entity, action, params or {})
98
98
  ```
99
99
 
100
+ ## Replication Configuration
101
+
102
+ This connector supports replication configuration for MULTI mode sources. See the [full reference documentation](./REFERENCE.md#replication-configuration) for details on available options like `start_date`.
103
+
100
104
  ## Full documentation
101
105
 
102
106
  This connector supports the following entities and actions.
@@ -125,6 +129,6 @@ For the service's official API docs, see the [Zendesk-Chat API reference](https:
125
129
 
126
130
  ## Version information
127
131
 
128
- - **Package version:** 0.1.9
129
- - **Connector version:** 0.1.3
130
- - **Generated with Connector SDK commit SHA:** 416466da4970ae5fd6c7f2c658a68e047e51efd9
132
+ - **Package version:** 0.1.12
133
+ - **Connector version:** 0.1.4
134
+ - **Generated with Connector SDK commit SHA:** b73c71e077aea8d316f7161dbd5014d4fb9f3098
@@ -1467,3 +1467,35 @@ curl --location 'https://api.airbyte.ai/api/v1/connectors/sources/{your_source_i
1467
1467
 
1468
1468
  </details>
1469
1469
 
1470
+
1471
+
1472
+ ## Replication Configuration
1473
+
1474
+ Settings for data replication from Zendesk Chat
1475
+
1476
+ These settings control how data is replicated from the source. Pass them via the `replication_config` parameter when creating a source.
1477
+
1478
+ | Field Name | Type | Required | Description |
1479
+ |------------|------|----------|-------------|
1480
+ | `start_date` | `str (date-time)` | Yes | The date from which to start replicating data, in the format YYYY-MM-DDT00:00:00Z. |
1481
+
1482
+ ### Example
1483
+
1484
+ **API**
1485
+
1486
+ ```bash
1487
+ curl --location 'https://api.airbyte.ai/integrations/connectors' \
1488
+ --header 'Content-Type: application/json' \
1489
+ --header 'Authorization: Bearer {your_auth_token}' \
1490
+ --data '{
1491
+ "external_user_id": "{your_external_user_id}",
1492
+ "connector_type": "zendesk-chat",
1493
+ "credentials": {
1494
+ ...
1495
+ },
1496
+ "replication_config": {
1497
+ "start_date": "<The date from which to start replicating data, in the format YYYY-MM-DDT00:00:00Z.>"
1498
+ },
1499
+ "name": "My Zendesk-Chat Connector"
1500
+ }'
1501
+ ```
@@ -14,10 +14,10 @@ from .models import (
14
14
  AgentRoles,
15
15
  AgentTimeline,
16
16
  Ban,
17
+ ChatEngagement,
17
18
  ChatConversion,
18
- ChatHistoryItem,
19
19
  WebpathItem,
20
- ChatEngagement,
20
+ ChatHistoryItem,
21
21
  Chat,
22
22
  Visitor,
23
23
  ChatSession,
@@ -109,10 +109,10 @@ __all__ = [
109
109
  "AgentRoles",
110
110
  "AgentTimeline",
111
111
  "Ban",
112
+ "ChatEngagement",
112
113
  "ChatConversion",
113
- "ChatHistoryItem",
114
114
  "WebpathItem",
115
- "ChatEngagement",
115
+ "ChatHistoryItem",
116
116
  "Chat",
117
117
  "Visitor",
118
118
  "ChatSession",
@@ -62,6 +62,53 @@ class TokenExtractValidationError(ConnectorModelLoaderError):
62
62
  pass
63
63
 
64
64
 
65
+ # Expected auth_mapping keys for each auth type.
66
+ # These are the auth parameters that each security scheme expects, NOT the user's credential field names.
67
+ EXPECTED_AUTH_MAPPING_KEYS: dict[AuthType, set[str]] = {
68
+ AuthType.BEARER: {"token"},
69
+ AuthType.BASIC: {"username", "password"},
70
+ AuthType.API_KEY: {"api_key"},
71
+ AuthType.OAUTH2: {"access_token", "refresh_token", "client_id", "client_secret"},
72
+ }
73
+
74
+
75
+ def _validate_auth_mapping_keys(
76
+ auth_type: AuthType,
77
+ auth_config: AirbyteAuthConfig | None,
78
+ scheme_name: str = "default",
79
+ ) -> None:
80
+ """Validate that auth_mapping keys match expected parameters for the auth type.
81
+
82
+ The auth_mapping keys must be the parameters expected by the security scheme
83
+ (e.g., "token" for bearer), not the user's credential field names.
84
+
85
+ Args:
86
+ auth_type: The authentication type
87
+ auth_config: The x-airbyte-auth-config containing auth_mapping
88
+ scheme_name: Name of the security scheme for error messages
89
+
90
+ Raises:
91
+ InvalidOpenAPIError: If auth_mapping keys don't match expected parameters
92
+ """
93
+ if auth_config is None or auth_config.auth_mapping is None:
94
+ return # No explicit auth_mapping, will use defaults
95
+
96
+ expected_keys = EXPECTED_AUTH_MAPPING_KEYS.get(auth_type)
97
+ if expected_keys is None:
98
+ return # Unknown auth type, skip validation
99
+
100
+ actual_keys = set(auth_config.auth_mapping.keys())
101
+ invalid_keys = actual_keys - expected_keys
102
+
103
+ if invalid_keys:
104
+ raise InvalidOpenAPIError(
105
+ f"Invalid auth_mapping keys for {auth_type.value} auth in scheme '{scheme_name}': {invalid_keys}. "
106
+ f"Expected keys for {auth_type.value}: {sorted(expected_keys)}. "
107
+ f"Note: auth_mapping keys must be the auth parameters (e.g., 'token' for bearer), "
108
+ f'not your credential field names. Use template syntax to map: token: "${{your_field}}"'
109
+ )
110
+
111
+
65
112
  def extract_path_params(path: str) -> list[str]:
66
113
  """Extract parameter names from path template.
67
114
 
@@ -145,6 +192,87 @@ def _deproxy_schema(obj: Any) -> Any:
145
192
  return obj
146
193
 
147
194
 
195
+ def _type_includes(type_value: Any, target: str) -> bool:
196
+ if isinstance(type_value, list):
197
+ return target in type_value
198
+ return type_value == target
199
+
200
+
201
+ def _flatten_cache_properties(properties: dict[str, Any], prefix: str) -> list[str]:
202
+ entries: list[str] = []
203
+ for prop_name, prop in properties.items():
204
+ path = f"{prefix}{prop_name}" if prefix else prop_name
205
+ entries.append(path)
206
+
207
+ prop_type = getattr(prop, "type", None) if not isinstance(prop, dict) else prop.get("type")
208
+ prop_properties = getattr(prop, "properties", None) if not isinstance(prop, dict) else prop.get("properties")
209
+
210
+ if _type_includes(prop_type, "array"):
211
+ array_path = f"{path}[]"
212
+ entries.append(array_path)
213
+ if isinstance(prop_properties, dict):
214
+ entries.extend(_flatten_cache_properties(prop_properties, prefix=f"{array_path}."))
215
+ elif isinstance(prop_properties, dict):
216
+ entries.extend(_flatten_cache_properties(prop_properties, prefix=f"{path}."))
217
+
218
+ return entries
219
+
220
+
221
+ def _flatten_cache_field_paths(field: Any) -> list[str]:
222
+ field_name = getattr(field, "name", None) if not isinstance(field, dict) else field.get("name")
223
+ if not isinstance(field_name, str) or not field_name:
224
+ return []
225
+
226
+ field_type = getattr(field, "type", None) if not isinstance(field, dict) else field.get("type")
227
+ field_properties = getattr(field, "properties", None) if not isinstance(field, dict) else field.get("properties")
228
+
229
+ entries = [field_name]
230
+ if _type_includes(field_type, "array"):
231
+ array_path = f"{field_name}[]"
232
+ entries.append(array_path)
233
+ if isinstance(field_properties, dict):
234
+ entries.extend(_flatten_cache_properties(field_properties, prefix=f"{array_path}."))
235
+ elif isinstance(field_properties, dict):
236
+ entries.extend(_flatten_cache_properties(field_properties, prefix=f"{field_name}."))
237
+
238
+ return entries
239
+
240
+
241
+ def _dedupe_strings(values: list[str]) -> list[str]:
242
+ seen: set[str] = set()
243
+ ordered: list[str] = []
244
+ for value in values:
245
+ if value not in seen:
246
+ seen.add(value)
247
+ ordered.append(value)
248
+ return ordered
249
+
250
+
251
+ def _extract_search_field_paths(spec: OpenAPIConnector) -> dict[str, list[str]]:
252
+ cache_config = getattr(spec.info, "x_airbyte_cache", None)
253
+ entities = getattr(cache_config, "entities", None)
254
+ if not isinstance(entities, list):
255
+ return {}
256
+
257
+ search_fields: dict[str, list[str]] = {}
258
+ for entity in entities:
259
+ entity_name = getattr(entity, "entity", None) if not isinstance(entity, dict) else entity.get("entity")
260
+ if not isinstance(entity_name, str) or not entity_name:
261
+ continue
262
+
263
+ fields = getattr(entity, "fields", None) if not isinstance(entity, dict) else entity.get("fields")
264
+ if not isinstance(fields, list):
265
+ continue
266
+
267
+ field_paths: list[str] = []
268
+ for field in fields:
269
+ field_paths.extend(_flatten_cache_field_paths(field))
270
+
271
+ search_fields[entity_name] = _dedupe_strings(field_paths)
272
+
273
+ return search_fields
274
+
275
+
148
276
  def parse_openapi_spec(raw_config: dict) -> OpenAPIConnector:
149
277
  """Parse OpenAPI specification from YAML.
150
278
 
@@ -434,6 +562,8 @@ def convert_openapi_to_connector_model(spec: OpenAPIConnector) -> ConnectorModel
434
562
  if not connector_id:
435
563
  raise InvalidOpenAPIError("Missing required x-airbyte-connector-id field")
436
564
 
565
+ search_field_paths = _extract_search_field_paths(spec)
566
+
437
567
  # Create ConnectorModel
438
568
  model = ConnectorModel(
439
569
  id=connector_id,
@@ -444,6 +574,7 @@ def convert_openapi_to_connector_model(spec: OpenAPIConnector) -> ConnectorModel
444
574
  entities=entities,
445
575
  openapi_spec=spec,
446
576
  retry_config=retry_config,
577
+ search_field_paths=search_field_paths,
447
578
  )
448
579
 
449
580
  return model
@@ -840,6 +971,9 @@ def _parse_single_security_scheme(scheme: Any) -> AuthConfig:
840
971
  oauth2_config = _parse_oauth2_config(scheme)
841
972
  # Use explicit x-airbyte-auth-config if present, otherwise generate default
842
973
  auth_config_obj = scheme.x_airbyte_auth_config or _generate_default_auth_config(AuthType.OAUTH2)
974
+ # Validate auth_mapping keys if explicitly provided
975
+ if scheme.x_airbyte_auth_config:
976
+ _validate_auth_mapping_keys(AuthType.OAUTH2, scheme.x_airbyte_auth_config)
843
977
  return AuthConfig(
844
978
  type=AuthType.OAUTH2,
845
979
  config=oauth2_config,
@@ -850,6 +984,10 @@ def _parse_single_security_scheme(scheme: Any) -> AuthConfig:
850
984
  # Use explicit x-airbyte-auth-config if present, otherwise generate default
851
985
  auth_config_obj = scheme.x_airbyte_auth_config or _generate_default_auth_config(auth_type)
852
986
 
987
+ # Validate auth_mapping keys if explicitly provided
988
+ if scheme.x_airbyte_auth_config:
989
+ _validate_auth_mapping_keys(auth_type, scheme.x_airbyte_auth_config)
990
+
853
991
  return AuthConfig(
854
992
  type=auth_type,
855
993
  config=auth_config,
@@ -1032,7 +1032,9 @@ class LocalExecutor:
1032
1032
  if "variables" in graphql_config and graphql_config["variables"]:
1033
1033
  variables = self._interpolate_variables(graphql_config["variables"], params, param_defaults)
1034
1034
  # Filter out None values (optional fields not provided) - matches REST _extract_body() behavior
1035
- body["variables"] = {k: v for k, v in variables.items() if v is not None}
1035
+ # But preserve None for variables explicitly marked as nullable (e.g., to unassign a user)
1036
+ nullable_vars = set(graphql_config.get("x-airbyte-nullable-variables") or [])
1037
+ body["variables"] = {k: v for k, v in variables.items() if v is not None or k in nullable_vars}
1036
1038
 
1037
1039
  # Add operation name if specified
1038
1040
  if "operationName" in graphql_config:
@@ -18,6 +18,185 @@ from typing import Any, Protocol
18
18
  MAX_EXAMPLE_QUESTIONS = 5 # Maximum number of example questions to include in description
19
19
 
20
20
 
21
+ def _type_includes(type_value: Any, target: str) -> bool:
22
+ if isinstance(type_value, list):
23
+ return target in type_value
24
+ return type_value == target
25
+
26
+
27
+ def _is_object_schema(schema: dict[str, Any]) -> bool:
28
+ if "properties" in schema:
29
+ return True
30
+ return _type_includes(schema.get("type"), "object")
31
+
32
+
33
+ def _is_array_schema(schema: dict[str, Any]) -> bool:
34
+ if "items" in schema:
35
+ return True
36
+ return _type_includes(schema.get("type"), "array")
37
+
38
+
39
+ def _dedupe_param_entries(entries: list[tuple[str, bool]]) -> list[tuple[str, bool]]:
40
+ seen: dict[str, bool] = {}
41
+ ordered: list[str] = []
42
+ for name, required in entries:
43
+ if name not in seen:
44
+ seen[name] = required
45
+ ordered.append(name)
46
+ else:
47
+ seen[name] = seen[name] or required
48
+ return [(name, seen[name]) for name in ordered]
49
+
50
+
51
+ def _flatten_schema_params(
52
+ schema: dict[str, Any],
53
+ prefix: str = "",
54
+ parent_required: bool = True,
55
+ seen_stack: set[int] | None = None,
56
+ ) -> list[tuple[str, bool]]:
57
+ if not isinstance(schema, dict):
58
+ return []
59
+
60
+ if seen_stack is None:
61
+ seen_stack = set()
62
+
63
+ schema_id = id(schema)
64
+ if schema_id in seen_stack:
65
+ return []
66
+
67
+ seen_stack.add(schema_id)
68
+ try:
69
+ entries: list[tuple[str, bool]] = []
70
+
71
+ for subschema in schema.get("allOf", []) or []:
72
+ if isinstance(subschema, dict):
73
+ entries.extend(_flatten_schema_params(subschema, prefix, parent_required, seen_stack))
74
+
75
+ for keyword in ("anyOf", "oneOf"):
76
+ for subschema in schema.get(keyword, []) or []:
77
+ if isinstance(subschema, dict):
78
+ entries.extend(_flatten_schema_params(subschema, prefix, False, seen_stack))
79
+
80
+ properties = schema.get("properties")
81
+ if isinstance(properties, dict):
82
+ required_fields = set(schema.get("required", [])) if isinstance(schema.get("required"), list) else set()
83
+ for prop_name, prop_schema in properties.items():
84
+ path = f"{prefix}{prop_name}" if prefix else prop_name
85
+ is_required = parent_required and prop_name in required_fields
86
+ entries.append((path, is_required))
87
+
88
+ if isinstance(prop_schema, dict):
89
+ if _is_array_schema(prop_schema):
90
+ array_path = f"{path}[]"
91
+ entries.append((array_path, is_required))
92
+ items = prop_schema.get("items")
93
+ if isinstance(items, dict):
94
+ entries.extend(_flatten_schema_params(items, prefix=f"{array_path}.", parent_required=is_required, seen_stack=seen_stack))
95
+ if _is_object_schema(prop_schema):
96
+ entries.extend(_flatten_schema_params(prop_schema, prefix=f"{path}.", parent_required=is_required, seen_stack=seen_stack))
97
+
98
+ return _dedupe_param_entries(entries)
99
+ finally:
100
+ seen_stack.remove(schema_id)
101
+
102
+
103
+ def _cache_field_value(field: Any, key: str) -> Any:
104
+ if isinstance(field, dict):
105
+ return field.get(key)
106
+ return getattr(field, key, None)
107
+
108
+
109
+ def _flatten_cache_properties(properties: dict[str, Any], prefix: str) -> list[str]:
110
+ entries: list[str] = []
111
+ for prop_name, prop in properties.items():
112
+ path = f"{prefix}{prop_name}" if prefix else prop_name
113
+ entries.append(path)
114
+
115
+ prop_type = _cache_field_value(prop, "type")
116
+ prop_properties = _cache_field_value(prop, "properties")
117
+
118
+ if _type_includes(prop_type, "array"):
119
+ array_path = f"{path}[]"
120
+ entries.append(array_path)
121
+ if isinstance(prop_properties, dict):
122
+ entries.extend(_flatten_cache_properties(prop_properties, prefix=f"{array_path}."))
123
+ elif isinstance(prop_properties, dict):
124
+ entries.extend(_flatten_cache_properties(prop_properties, prefix=f"{path}."))
125
+
126
+ return entries
127
+
128
+
129
+ def _flatten_cache_field_paths(field: Any) -> list[str]:
130
+ field_name = _cache_field_value(field, "name")
131
+ if not isinstance(field_name, str) or not field_name:
132
+ return []
133
+
134
+ field_type = _cache_field_value(field, "type")
135
+ field_properties = _cache_field_value(field, "properties")
136
+
137
+ entries = [field_name]
138
+ if _type_includes(field_type, "array"):
139
+ array_path = f"{field_name}[]"
140
+ entries.append(array_path)
141
+ if isinstance(field_properties, dict):
142
+ entries.extend(_flatten_cache_properties(field_properties, prefix=f"{array_path}."))
143
+ elif isinstance(field_properties, dict):
144
+ entries.extend(_flatten_cache_properties(field_properties, prefix=f"{field_name}."))
145
+
146
+ return entries
147
+
148
+
149
+ def _dedupe_strings(values: list[str]) -> list[str]:
150
+ seen: set[str] = set()
151
+ ordered: list[str] = []
152
+ for value in values:
153
+ if value not in seen:
154
+ seen.add(value)
155
+ ordered.append(value)
156
+ return ordered
157
+
158
+
159
+ def _collect_search_field_paths(model: ConnectorModelProtocol) -> dict[str, list[str]]:
160
+ search_field_paths = getattr(model, "search_field_paths", None)
161
+ if isinstance(search_field_paths, dict) and search_field_paths:
162
+ normalized: dict[str, list[str]] = {}
163
+ for entity, fields in search_field_paths.items():
164
+ if not isinstance(entity, str) or not entity:
165
+ continue
166
+ if isinstance(fields, list):
167
+ normalized[entity] = _dedupe_strings([field for field in fields if isinstance(field, str) and field])
168
+ return normalized
169
+
170
+ openapi_spec = getattr(model, "openapi_spec", None)
171
+ info = getattr(openapi_spec, "info", None)
172
+ cache_config = getattr(info, "x_airbyte_cache", None)
173
+ entities = getattr(cache_config, "entities", None)
174
+ if not isinstance(entities, list):
175
+ return {}
176
+
177
+ search_fields: dict[str, list[str]] = {}
178
+ for entity in entities:
179
+ entity_name = _cache_field_value(entity, "entity")
180
+ if not isinstance(entity_name, str) or not entity_name:
181
+ continue
182
+
183
+ fields = _cache_field_value(entity, "fields") or []
184
+ if not isinstance(fields, list):
185
+ continue
186
+ field_paths: list[str] = []
187
+ for field in fields:
188
+ field_paths.extend(_flatten_cache_field_paths(field))
189
+
190
+ search_fields[entity_name] = _dedupe_strings(field_paths)
191
+
192
+ return search_fields
193
+
194
+
195
+ def _format_search_param_signature() -> str:
196
+ params = ["query*", "limit?", "cursor?", "fields?"]
197
+ return f"({', '.join(params)})"
198
+
199
+
21
200
  class EndpointProtocol(Protocol):
22
201
  """Protocol defining the expected interface for endpoint parameters.
23
202
 
@@ -54,6 +233,9 @@ class ConnectorModelProtocol(Protocol):
54
233
  @property
55
234
  def openapi_spec(self) -> Any: ...
56
235
 
236
+ @property
237
+ def search_field_paths(self) -> dict[str, list[str]] | None: ...
238
+
57
239
 
58
240
  def format_param_signature(endpoint: EndpointProtocol) -> str:
59
241
  """Format parameter signature for an endpoint action.
@@ -86,9 +268,12 @@ def format_param_signature(endpoint: EndpointProtocol) -> str:
86
268
  required = schema.get("required", False)
87
269
  params.append(f"{name}{'*' if required else '?'}")
88
270
 
89
- # Body fields
90
- if request_schema:
91
- required_fields = set(request_schema.get("required", []))
271
+ # Body fields (include nested params from schema when available)
272
+ if isinstance(request_schema, dict):
273
+ for name, required in _flatten_schema_params(request_schema):
274
+ params.append(f"{name}{'*' if required else '?'}")
275
+ elif request_schema:
276
+ required_fields = set(request_schema.get("required", [])) if isinstance(request_schema, dict) else set()
92
277
  for name in body_fields:
93
278
  params.append(f"{name}{'*' if name in required_fields else '?'}")
94
279
 
@@ -99,7 +284,7 @@ def describe_entities(model: ConnectorModelProtocol) -> list[dict[str, Any]]:
99
284
  """Generate entity descriptions from ConnectorModel.
100
285
 
101
286
  Returns a list of entity descriptions with detailed parameter information
102
- for each action. This is used by generated connectors' describe() method.
287
+ for each action. This is used by generated connectors' list_entities() method.
103
288
 
104
289
  Args:
105
290
  model: Object conforming to ConnectorModelProtocol (e.g., ConnectorModel)
@@ -203,8 +388,8 @@ def generate_tool_description(model: ConnectorModelProtocol) -> str:
203
388
  - Response structure documentation with pagination hints
204
389
  - Example questions if available in the OpenAPI spec
205
390
 
206
- This is used by the Connector.describe class method decorator to populate
207
- function docstrings for AI framework integration.
391
+ This is used by the Connector.tool_utils decorator to populate function
392
+ docstrings for AI framework integration.
208
393
 
209
394
  Args:
210
395
  model: Object conforming to ConnectorModelProtocol (e.g., ConnectorModel)
@@ -213,8 +398,11 @@ def generate_tool_description(model: ConnectorModelProtocol) -> str:
213
398
  Formatted description string suitable for AI tool documentation
214
399
  """
215
400
  lines = []
401
+ # NOTE: Do not insert blank lines in the docstring; pydantic-ai parsing truncates
402
+ # at the first empty line and only keeps the initial section.
216
403
 
217
404
  # Entity/action parameter details (including pagination params like limit, starting_after)
405
+ search_field_paths = _collect_search_field_paths(model)
218
406
  lines.append("ENTITIES AND PARAMETERS:")
219
407
  for entity in model.entities:
220
408
  lines.append(f" {entity.name}:")
@@ -228,14 +416,41 @@ def generate_tool_description(model: ConnectorModelProtocol) -> str:
228
416
  lines.append(f" - {action_str}{param_sig}")
229
417
  else:
230
418
  lines.append(f" - {action_str}()")
419
+ if entity.name in search_field_paths:
420
+ search_sig = _format_search_param_signature()
421
+ lines.append(f" - search{search_sig}")
231
422
 
232
423
  # Response structure (brief, includes pagination hint)
233
- lines.append("")
234
424
  lines.append("RESPONSE STRUCTURE:")
235
425
  lines.append(" - list/api_search: {data: [...], meta: {has_more: bool}}")
236
426
  lines.append(" - get: Returns entity directly (no envelope)")
237
427
  lines.append(" To paginate: pass starting_after=<last_id> while has_more is true")
238
428
 
429
+ lines.append("GUIDELINES:")
430
+ lines.append(' - Prefer cached search over direct API calls when using execute(): action="search" whenever possible.')
431
+ lines.append(" - Direct API actions (list/get/download) are slower and should be used only if search cannot answer the query.")
432
+ lines.append(" - Keep results small: use params.fields, params.query.filter, small params.limit, and cursor pagination.")
433
+ lines.append(" - If output is too large, refine the query with tighter filters/fields/limit.")
434
+
435
+ if search_field_paths:
436
+ lines.append("SEARCH (PREFERRED):")
437
+ lines.append(' execute(entity, action="search", params={')
438
+ lines.append(' "query": {"filter": <condition>, "sort": [{"field": "asc|desc"}, ...]},')
439
+ lines.append(' "limit": <int>, "cursor": <str>, "fields": ["field", "nested.field", ...]')
440
+ lines.append(" })")
441
+ lines.append(' Example: {"query": {"filter": {"eq": {"title": "Intro to Airbyte | Miinto"}}}, "limit": 1,')
442
+ lines.append(' "fields": ["id", "title", "started", "primaryUserId"]}')
443
+ lines.append(" Conditions are composable:")
444
+ lines.append(" - eq, neq, gt, gte, lt, lte, in, like, fuzzy, keyword, contains, any")
445
+ lines.append(' - and/or/not to combine conditions (e.g., {"and": [cond1, cond2]})')
446
+
447
+ lines.append("SEARCHABLE FIELDS:")
448
+ for entity_name, field_paths in search_field_paths.items():
449
+ if field_paths:
450
+ lines.append(f" {entity_name}: {', '.join(field_paths)}")
451
+ else:
452
+ lines.append(f" {entity_name}: (no fields listed)")
453
+
239
454
  # Add example questions if available in openapi_spec
240
455
  openapi_spec = getattr(model, "openapi_spec", None)
241
456
  if openapi_spec:
@@ -245,18 +460,15 @@ def generate_tool_description(model: ConnectorModelProtocol) -> str:
245
460
  if example_questions:
246
461
  supported = getattr(example_questions, "supported", None)
247
462
  if supported:
248
- lines.append("")
249
463
  lines.append("EXAMPLE QUESTIONS:")
250
464
  for q in supported[:MAX_EXAMPLE_QUESTIONS]:
251
465
  lines.append(f" - {q}")
252
466
 
253
467
  # Generic parameter description for function signature
254
- lines.append("")
255
468
  lines.append("FUNCTION PARAMETERS:")
256
469
  lines.append(" - entity: Entity name (string)")
257
470
  lines.append(" - action: Operation to perform (string)")
258
471
  lines.append(" - params: Operation parameters (dict) - see entity details above")
259
- lines.append("")
260
472
  lines.append("Parameter markers: * = required, ? = optional")
261
473
 
262
474
  return "\n".join(lines)