airbyte-agent-salesforce 0.1.14__tar.gz → 0.1.25__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.
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/CHANGELOG.md +55 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/PKG-INFO +13 -8
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/README.md +12 -7
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/connector_model_loader.py +10 -2
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/executor/local_executor.py +90 -20
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/extensions.py +39 -0
- airbyte_agent_salesforce-0.1.25/airbyte_agent_salesforce/_vendored/connector_sdk/introspection.py +262 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/schema/components.py +2 -1
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/schema/security.py +10 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/types.py +4 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/connector.py +89 -4
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/connector_model.py +1 -1
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/pyproject.toml +1 -1
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/.gitignore +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/REFERENCE.md +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/__init__.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/__init__.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/__init__.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/auth_strategies.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/auth_template.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/cloud_utils/__init__.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/cloud_utils/client.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/constants.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/exceptions.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/executor/__init__.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/executor/hosted_executor.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/executor/models.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/http/__init__.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/http/adapters/__init__.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/http/adapters/httpx_adapter.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/http/config.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/http/exceptions.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/http/protocols.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/http/response.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/http_client.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/logging/__init__.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/logging/logger.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/logging/types.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/observability/__init__.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/observability/models.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/observability/redactor.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/observability/session.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/performance/__init__.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/performance/instrumentation.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/performance/metrics.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/schema/__init__.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/schema/base.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/schema/connector.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/schema/extensions.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/schema/operations.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/secrets.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/telemetry/__init__.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/telemetry/config.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/telemetry/events.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/telemetry/tracker.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/utils.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/_vendored/connector_sdk/validation.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/models.py +0 -0
- {airbyte_agent_salesforce-0.1.14 → airbyte_agent_salesforce-0.1.25}/airbyte_agent_salesforce/types.py +0 -0
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# Salesforce changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.25] - 2026-01-09
|
|
4
|
+
- Updated connector definition (YAML version 1.0.4)
|
|
5
|
+
- Source commit: 3c7bfdfd
|
|
6
|
+
- SDK version: 0.1.0
|
|
7
|
+
|
|
8
|
+
## [0.1.24] - 2026-01-09
|
|
9
|
+
- Updated connector definition (YAML version 1.0.4)
|
|
10
|
+
- Source commit: 3bcb33e8
|
|
11
|
+
- SDK version: 0.1.0
|
|
12
|
+
|
|
13
|
+
## [0.1.23] - 2026-01-09
|
|
14
|
+
- Updated connector definition (YAML version 1.0.4)
|
|
15
|
+
- Source commit: da9b741b
|
|
16
|
+
- SDK version: 0.1.0
|
|
17
|
+
|
|
18
|
+
## [0.1.22] - 2026-01-07
|
|
19
|
+
- Updated connector definition (YAML version 1.0.4)
|
|
20
|
+
- Source commit: d023e05f
|
|
21
|
+
- SDK version: 0.1.0
|
|
22
|
+
|
|
23
|
+
## [0.1.21] - 2026-01-06
|
|
24
|
+
- Updated connector definition (YAML version 1.0.4)
|
|
25
|
+
- Source commit: 0580c727
|
|
26
|
+
- SDK version: 0.1.0
|
|
27
|
+
|
|
28
|
+
## [0.1.20] - 2026-01-06
|
|
29
|
+
- Updated connector definition (YAML version 1.0.4)
|
|
30
|
+
- Source commit: e0e2f989
|
|
31
|
+
- SDK version: 0.1.0
|
|
32
|
+
|
|
33
|
+
## [0.1.19] - 2026-01-05
|
|
34
|
+
- Updated connector definition (YAML version 1.0.4)
|
|
35
|
+
- Source commit: 3e274293
|
|
36
|
+
- SDK version: 0.1.0
|
|
37
|
+
|
|
38
|
+
## [0.1.18] - 2025-12-22
|
|
39
|
+
- Updated connector definition (YAML version 1.0.4)
|
|
40
|
+
- Source commit: 0eb1b1c4
|
|
41
|
+
- SDK version: 0.1.0
|
|
42
|
+
|
|
43
|
+
## [0.1.17] - 2025-12-19
|
|
44
|
+
- Updated connector definition (YAML version 1.0.3)
|
|
45
|
+
- Source commit: 12f6b994
|
|
46
|
+
- SDK version: 0.1.0
|
|
47
|
+
|
|
48
|
+
## [0.1.16] - 2025-12-19
|
|
49
|
+
- Updated connector definition (YAML version 1.0.3)
|
|
50
|
+
- Source commit: 5d11bfdf
|
|
51
|
+
- SDK version: 0.1.0
|
|
52
|
+
|
|
53
|
+
## [0.1.15] - 2025-12-19
|
|
54
|
+
- Updated connector definition (YAML version 1.0.3)
|
|
55
|
+
- Source commit: e996e848
|
|
56
|
+
- SDK version: 0.1.0
|
|
57
|
+
|
|
3
58
|
## [0.1.14] - 2025-12-18
|
|
4
59
|
- Updated connector definition (YAML version 1.0.3)
|
|
5
60
|
- Source commit: f7c55d3e
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: airbyte-agent-salesforce
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.25
|
|
4
4
|
Summary: Airbyte Salesforce Connector for AI platforms
|
|
5
5
|
Project-URL: Homepage, https://github.com/airbytehq/airbyte-embedded
|
|
6
6
|
Project-URL: Documentation, https://github.com/airbytehq/airbyte-embedded/tree/main/integrations
|
|
@@ -42,21 +42,25 @@ notes, and attachments for sales analytics and customer relationship management.
|
|
|
42
42
|
|
|
43
43
|
## Example questions
|
|
44
44
|
|
|
45
|
+
The Salesforce connector is optimized to handle prompts like these.
|
|
46
|
+
|
|
45
47
|
- Show me my top 5 opportunities this month
|
|
46
|
-
- List all contacts from
|
|
48
|
+
- List all contacts from \{company\} in the last quarter
|
|
47
49
|
- Search for leads in the technology sector with revenue over $10M
|
|
48
50
|
- What trends can you identify in my recent sales pipeline?
|
|
49
51
|
- Summarize the open cases for my key accounts
|
|
50
52
|
- Find upcoming events related to my most important opportunities
|
|
51
53
|
- Analyze the performance of my recent marketing campaigns
|
|
52
54
|
- Identify the highest value opportunities I'm currently tracking
|
|
53
|
-
- Show me the notes and attachments for
|
|
55
|
+
- Show me the notes and attachments for \{customer\}'s account
|
|
54
56
|
|
|
55
57
|
## Unsupported questions
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
The Salesforce connector isn't currently able to handle prompts like these.
|
|
60
|
+
|
|
61
|
+
- Create a new lead for \{person\}
|
|
58
62
|
- Update the status of my sales opportunity
|
|
59
|
-
- Schedule a follow-up meeting with
|
|
63
|
+
- Schedule a follow-up meeting with \{customer\}
|
|
60
64
|
- Delete this old contact record
|
|
61
65
|
- Send an email to all contacts in this campaign
|
|
62
66
|
|
|
@@ -81,6 +85,7 @@ connector = SalesforceConnector(
|
|
|
81
85
|
result = await connector.accounts.list()
|
|
82
86
|
```
|
|
83
87
|
|
|
88
|
+
|
|
84
89
|
## Full documentation
|
|
85
90
|
|
|
86
91
|
This connector supports the following entities and actions.
|
|
@@ -107,6 +112,6 @@ For the service's official API docs, see the [Salesforce API reference](https://
|
|
|
107
112
|
|
|
108
113
|
## Version information
|
|
109
114
|
|
|
110
|
-
- **Package version:** 0.1.
|
|
111
|
-
- **Connector version:** 1.0.
|
|
112
|
-
- **Generated with Connector SDK commit SHA:**
|
|
115
|
+
- **Package version:** 0.1.25
|
|
116
|
+
- **Connector version:** 1.0.4
|
|
117
|
+
- **Generated with Connector SDK commit SHA:** 3c7bfdfd1100dd20420a61cec56549b65820ea0f
|
|
@@ -8,21 +8,25 @@ notes, and attachments for sales analytics and customer relationship management.
|
|
|
8
8
|
|
|
9
9
|
## Example questions
|
|
10
10
|
|
|
11
|
+
The Salesforce connector is optimized to handle prompts like these.
|
|
12
|
+
|
|
11
13
|
- Show me my top 5 opportunities this month
|
|
12
|
-
- List all contacts from
|
|
14
|
+
- List all contacts from \{company\} in the last quarter
|
|
13
15
|
- Search for leads in the technology sector with revenue over $10M
|
|
14
16
|
- What trends can you identify in my recent sales pipeline?
|
|
15
17
|
- Summarize the open cases for my key accounts
|
|
16
18
|
- Find upcoming events related to my most important opportunities
|
|
17
19
|
- Analyze the performance of my recent marketing campaigns
|
|
18
20
|
- Identify the highest value opportunities I'm currently tracking
|
|
19
|
-
- Show me the notes and attachments for
|
|
21
|
+
- Show me the notes and attachments for \{customer\}'s account
|
|
20
22
|
|
|
21
23
|
## Unsupported questions
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
The Salesforce connector isn't currently able to handle prompts like these.
|
|
26
|
+
|
|
27
|
+
- Create a new lead for \{person\}
|
|
24
28
|
- Update the status of my sales opportunity
|
|
25
|
-
- Schedule a follow-up meeting with
|
|
29
|
+
- Schedule a follow-up meeting with \{customer\}
|
|
26
30
|
- Delete this old contact record
|
|
27
31
|
- Send an email to all contacts in this campaign
|
|
28
32
|
|
|
@@ -47,6 +51,7 @@ connector = SalesforceConnector(
|
|
|
47
51
|
result = await connector.accounts.list()
|
|
48
52
|
```
|
|
49
53
|
|
|
54
|
+
|
|
50
55
|
## Full documentation
|
|
51
56
|
|
|
52
57
|
This connector supports the following entities and actions.
|
|
@@ -73,6 +78,6 @@ For the service's official API docs, see the [Salesforce API reference](https://
|
|
|
73
78
|
|
|
74
79
|
## Version information
|
|
75
80
|
|
|
76
|
-
- **Package version:** 0.1.
|
|
77
|
-
- **Connector version:** 1.0.
|
|
78
|
-
- **Generated with Connector SDK commit SHA:**
|
|
81
|
+
- **Package version:** 0.1.25
|
|
82
|
+
- **Connector version:** 1.0.4
|
|
83
|
+
- **Generated with Connector SDK commit SHA:** 3c7bfdfd1100dd20420a61cec56549b65820ea0f
|
|
@@ -393,16 +393,24 @@ def convert_openapi_to_connector_model(spec: OpenAPIConnector) -> ConnectorModel
|
|
|
393
393
|
for entity_name, endpoints_dict in entities_map.items():
|
|
394
394
|
actions = list(endpoints_dict.keys())
|
|
395
395
|
|
|
396
|
-
# Get schema from components if available
|
|
396
|
+
# Get schema and stream_name from components if available
|
|
397
397
|
schema = None
|
|
398
|
+
entity_stream_name = None
|
|
398
399
|
if spec.components:
|
|
399
400
|
# Look for a schema matching the entity name
|
|
400
401
|
for schema_name, schema_def in spec.components.schemas.items():
|
|
401
402
|
if schema_def.x_airbyte_entity_name == entity_name or schema_name.lower() == entity_name.lower():
|
|
402
403
|
schema = schema_def.model_dump(by_alias=True)
|
|
404
|
+
entity_stream_name = schema_def.x_airbyte_stream_name
|
|
403
405
|
break
|
|
404
406
|
|
|
405
|
-
entity = EntityDefinition(
|
|
407
|
+
entity = EntityDefinition(
|
|
408
|
+
name=entity_name,
|
|
409
|
+
stream_name=entity_stream_name,
|
|
410
|
+
actions=actions,
|
|
411
|
+
endpoints=endpoints_dict,
|
|
412
|
+
schema=schema,
|
|
413
|
+
)
|
|
406
414
|
entities.append(entity)
|
|
407
415
|
|
|
408
416
|
# Extract retry config from x-airbyte-retry-config extension
|
|
@@ -674,16 +674,16 @@ class LocalExecutor:
|
|
|
674
674
|
return {key: value for key, value in params.items() if key in allowed_params}
|
|
675
675
|
|
|
676
676
|
def _extract_body(self, allowed_fields: list[str], params: dict[str, Any]) -> dict[str, Any]:
|
|
677
|
-
"""Extract body fields from params.
|
|
677
|
+
"""Extract body fields from params, filtering out None values.
|
|
678
678
|
|
|
679
679
|
Args:
|
|
680
680
|
allowed_fields: List of allowed body field names
|
|
681
681
|
params: All parameters
|
|
682
682
|
|
|
683
683
|
Returns:
|
|
684
|
-
Dictionary of body fields
|
|
684
|
+
Dictionary of body fields with None values filtered out
|
|
685
685
|
"""
|
|
686
|
-
return {key: value for key, value in params.items() if key in allowed_fields}
|
|
686
|
+
return {key: value for key, value in params.items() if key in allowed_fields and value is not None}
|
|
687
687
|
|
|
688
688
|
def _serialize_deep_object_params(self, params: dict[str, Any], deep_object_param_names: list[str]) -> dict[str, Any]:
|
|
689
689
|
"""Serialize deepObject parameters to bracket notation format.
|
|
@@ -837,15 +837,65 @@ class LocalExecutor:
|
|
|
837
837
|
Request body dict or None if no body needed
|
|
838
838
|
"""
|
|
839
839
|
if endpoint.graphql_body:
|
|
840
|
-
|
|
840
|
+
# Extract defaults from query_params_schema for GraphQL variable interpolation
|
|
841
|
+
param_defaults = {name: schema.get("default") for name, schema in endpoint.query_params_schema.items() if "default" in schema}
|
|
842
|
+
return self._build_graphql_body(endpoint.graphql_body, params, param_defaults)
|
|
841
843
|
elif endpoint.body_fields:
|
|
842
844
|
return self._extract_body(endpoint.body_fields, params)
|
|
843
845
|
return None
|
|
844
846
|
|
|
847
|
+
def _flatten_form_data(self, data: dict[str, Any], parent_key: str = "") -> dict[str, Any]:
|
|
848
|
+
"""Flatten nested dict/list structures into bracket notation for form encoding.
|
|
849
|
+
|
|
850
|
+
Stripe and similar APIs require nested arrays/objects to be encoded using bracket
|
|
851
|
+
notation when using application/x-www-form-urlencoded content type.
|
|
852
|
+
|
|
853
|
+
Args:
|
|
854
|
+
data: Nested dict with arrays/objects to flatten
|
|
855
|
+
parent_key: Parent key for nested structures (used in recursion)
|
|
856
|
+
|
|
857
|
+
Returns:
|
|
858
|
+
Flattened dict with bracket notation keys
|
|
859
|
+
|
|
860
|
+
Examples:
|
|
861
|
+
>>> _flatten_form_data({"items": [{"price": "p1", "qty": 1}]})
|
|
862
|
+
{"items[0][price]": "p1", "items[0][qty]": 1}
|
|
863
|
+
|
|
864
|
+
>>> _flatten_form_data({"customer": "cus_123", "metadata": {"key": "value"}})
|
|
865
|
+
{"customer": "cus_123", "metadata[key]": "value"}
|
|
866
|
+
"""
|
|
867
|
+
flattened = {}
|
|
868
|
+
|
|
869
|
+
for key, value in data.items():
|
|
870
|
+
new_key = f"{parent_key}[{key}]" if parent_key else key
|
|
871
|
+
|
|
872
|
+
if isinstance(value, dict):
|
|
873
|
+
# Recursively flatten nested dicts
|
|
874
|
+
flattened.update(self._flatten_form_data(value, new_key))
|
|
875
|
+
elif isinstance(value, list):
|
|
876
|
+
# Flatten arrays with indexed bracket notation
|
|
877
|
+
for i, item in enumerate(value):
|
|
878
|
+
indexed_key = f"{new_key}[{i}]"
|
|
879
|
+
if isinstance(item, dict):
|
|
880
|
+
# Nested dict in array - recurse
|
|
881
|
+
flattened.update(self._flatten_form_data(item, indexed_key))
|
|
882
|
+
elif isinstance(item, list):
|
|
883
|
+
# Nested list in array - recurse
|
|
884
|
+
flattened.update(self._flatten_form_data({str(i): item}, new_key))
|
|
885
|
+
else:
|
|
886
|
+
# Primitive value in array
|
|
887
|
+
flattened[indexed_key] = item
|
|
888
|
+
else:
|
|
889
|
+
# Primitive value - add directly
|
|
890
|
+
flattened[new_key] = value
|
|
891
|
+
|
|
892
|
+
return flattened
|
|
893
|
+
|
|
845
894
|
def _determine_request_format(self, endpoint: EndpointDefinition, body: dict[str, Any] | None) -> dict[str, Any]:
|
|
846
895
|
"""Determine json/data parameters for HTTP request.
|
|
847
896
|
|
|
848
897
|
GraphQL always uses JSON, regardless of content_type setting.
|
|
898
|
+
For form-encoded requests, nested structures are flattened into bracket notation.
|
|
849
899
|
|
|
850
900
|
Args:
|
|
851
901
|
endpoint: Endpoint definition
|
|
@@ -862,7 +912,9 @@ class LocalExecutor:
|
|
|
862
912
|
if is_graphql or endpoint.content_type.value == "application/json":
|
|
863
913
|
return {"json": body}
|
|
864
914
|
elif endpoint.content_type.value == "application/x-www-form-urlencoded":
|
|
865
|
-
|
|
915
|
+
# Flatten nested structures for form encoding
|
|
916
|
+
flattened_body = self._flatten_form_data(body)
|
|
917
|
+
return {"data": flattened_body}
|
|
866
918
|
|
|
867
919
|
return {}
|
|
868
920
|
|
|
@@ -903,12 +955,18 @@ class LocalExecutor:
|
|
|
903
955
|
|
|
904
956
|
return query
|
|
905
957
|
|
|
906
|
-
def _build_graphql_body(
|
|
958
|
+
def _build_graphql_body(
|
|
959
|
+
self,
|
|
960
|
+
graphql_config: dict[str, Any],
|
|
961
|
+
params: dict[str, Any],
|
|
962
|
+
param_defaults: dict[str, Any] | None = None,
|
|
963
|
+
) -> dict[str, Any]:
|
|
907
964
|
"""Build GraphQL request body with variable substitution and field selection.
|
|
908
965
|
|
|
909
966
|
Args:
|
|
910
967
|
graphql_config: GraphQL configuration from x-airbyte-body-type extension
|
|
911
968
|
params: Parameters from execute() call
|
|
969
|
+
param_defaults: Default values for params from query_params_schema
|
|
912
970
|
|
|
913
971
|
Returns:
|
|
914
972
|
GraphQL request body: {"query": "...", "variables": {...}}
|
|
@@ -922,7 +980,7 @@ class LocalExecutor:
|
|
|
922
980
|
|
|
923
981
|
# Substitute variables from params
|
|
924
982
|
if "variables" in graphql_config and graphql_config["variables"]:
|
|
925
|
-
body["variables"] = self._interpolate_variables(graphql_config["variables"], params)
|
|
983
|
+
body["variables"] = self._interpolate_variables(graphql_config["variables"], params, param_defaults)
|
|
926
984
|
|
|
927
985
|
# Add operation name if specified
|
|
928
986
|
if "operationName" in graphql_config:
|
|
@@ -981,7 +1039,12 @@ class LocalExecutor:
|
|
|
981
1039
|
fields_str = " ".join(graphql_fields)
|
|
982
1040
|
return query.replace("{{ fields }}", fields_str)
|
|
983
1041
|
|
|
984
|
-
def _interpolate_variables(
|
|
1042
|
+
def _interpolate_variables(
|
|
1043
|
+
self,
|
|
1044
|
+
variables: dict[str, Any],
|
|
1045
|
+
params: dict[str, Any],
|
|
1046
|
+
param_defaults: dict[str, Any] | None = None,
|
|
1047
|
+
) -> dict[str, Any]:
|
|
985
1048
|
"""Recursively interpolate variables using params.
|
|
986
1049
|
|
|
987
1050
|
Preserves types (doesn't stringify everything).
|
|
@@ -990,15 +1053,18 @@ class LocalExecutor:
|
|
|
990
1053
|
- Direct replacement: "{{ owner }}" → params["owner"] (preserves type)
|
|
991
1054
|
- Nested objects: {"input": {"name": "{{ name }}"}}
|
|
992
1055
|
- Arrays: [{"id": "{{ id }}"}]
|
|
993
|
-
-
|
|
1056
|
+
- Default values: "{{ per_page }}" → param_defaults["per_page"] if not in params
|
|
1057
|
+
- Unsubstituted placeholders: "{{ states }}" → None (for optional params without defaults)
|
|
994
1058
|
|
|
995
1059
|
Args:
|
|
996
1060
|
variables: Variables dict with template placeholders
|
|
997
1061
|
params: Parameters to substitute
|
|
1062
|
+
param_defaults: Default values for params from query_params_schema
|
|
998
1063
|
|
|
999
1064
|
Returns:
|
|
1000
1065
|
Interpolated variables dict with types preserved
|
|
1001
1066
|
"""
|
|
1067
|
+
defaults = param_defaults or {}
|
|
1002
1068
|
|
|
1003
1069
|
def interpolate_value(value: Any) -> Any:
|
|
1004
1070
|
if isinstance(value, str):
|
|
@@ -1012,8 +1078,15 @@ class LocalExecutor:
|
|
|
1012
1078
|
value = value.replace(placeholder, str(param_value))
|
|
1013
1079
|
|
|
1014
1080
|
# Check if any unsubstituted placeholders remain
|
|
1015
|
-
# If so, return None (treats as "not provided" for optional params)
|
|
1016
1081
|
if re.search(r"\{\{\s*\w+\s*\}\}", value):
|
|
1082
|
+
# Extract placeholder name and check for default value
|
|
1083
|
+
match = re.search(r"\{\{\s*(\w+)\s*\}\}", value)
|
|
1084
|
+
if match:
|
|
1085
|
+
param_name = match.group(1)
|
|
1086
|
+
if param_name in defaults:
|
|
1087
|
+
# Use default value (preserves type)
|
|
1088
|
+
return defaults[param_name]
|
|
1089
|
+
# No default found - return None (for optional params)
|
|
1017
1090
|
return None
|
|
1018
1091
|
|
|
1019
1092
|
return value
|
|
@@ -1151,21 +1224,18 @@ class LocalExecutor:
|
|
|
1151
1224
|
if action not in (Action.CREATE, Action.UPDATE):
|
|
1152
1225
|
return
|
|
1153
1226
|
|
|
1154
|
-
#
|
|
1155
|
-
|
|
1227
|
+
# Get the request schema to find truly required fields
|
|
1228
|
+
request_schema = endpoint.request_schema
|
|
1229
|
+
if not request_schema:
|
|
1156
1230
|
return
|
|
1157
1231
|
|
|
1158
|
-
#
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
missing_fields = []
|
|
1162
|
-
for field in endpoint.body_fields:
|
|
1163
|
-
if field not in params:
|
|
1164
|
-
missing_fields.append(field)
|
|
1232
|
+
# Only validate fields explicitly marked as required in the schema
|
|
1233
|
+
required_fields = request_schema.get("required", [])
|
|
1234
|
+
missing_fields = [field for field in required_fields if field not in params]
|
|
1165
1235
|
|
|
1166
1236
|
if missing_fields:
|
|
1167
1237
|
raise MissingParameterError(
|
|
1168
|
-
f"Missing required body fields for {entity}.{action.value}: {missing_fields}. Provided parameters: {list(params.keys())}"
|
|
1238
|
+
f"Missing required body fields for {entity}.{action.value}: {missing_fields}. " f"Provided parameters: {list(params.keys())}"
|
|
1169
1239
|
)
|
|
1170
1240
|
|
|
1171
1241
|
async def close(self):
|
|
@@ -159,6 +159,38 @@ Example:
|
|
|
159
159
|
```
|
|
160
160
|
"""
|
|
161
161
|
|
|
162
|
+
AIRBYTE_STREAM_NAME = "x-airbyte-stream-name"
|
|
163
|
+
"""
|
|
164
|
+
Extension: x-airbyte-stream-name
|
|
165
|
+
Location: Schema object (in components.schemas)
|
|
166
|
+
Type: string
|
|
167
|
+
Required: No
|
|
168
|
+
|
|
169
|
+
Description:
|
|
170
|
+
Specifies the Airbyte stream name for cache lookup purposes. This maps the entity
|
|
171
|
+
to the corresponding Airbyte stream, enabling cache-based data retrieval. When
|
|
172
|
+
specified, the EntityDefinition.stream_name field will be populated with this value.
|
|
173
|
+
|
|
174
|
+
This extension is placed on Schema objects alongside x-airbyte-entity-name, following
|
|
175
|
+
the same pattern. The stream name is an entity-level property (not operation-level)
|
|
176
|
+
since an entity maps to exactly one Airbyte stream.
|
|
177
|
+
|
|
178
|
+
Example:
|
|
179
|
+
```yaml
|
|
180
|
+
components:
|
|
181
|
+
schemas:
|
|
182
|
+
Customer:
|
|
183
|
+
type: object
|
|
184
|
+
x-airbyte-entity-name: customers
|
|
185
|
+
x-airbyte-stream-name: customers
|
|
186
|
+
properties:
|
|
187
|
+
id:
|
|
188
|
+
type: string
|
|
189
|
+
name:
|
|
190
|
+
type: string
|
|
191
|
+
```
|
|
192
|
+
"""
|
|
193
|
+
|
|
162
194
|
AIRBYTE_TOKEN_PATH = "x-airbyte-token-path"
|
|
163
195
|
"""
|
|
164
196
|
Extension: x-airbyte-token-path
|
|
@@ -548,6 +580,7 @@ def get_all_extension_names() -> list[str]:
|
|
|
548
580
|
AIRBYTE_ENTITY,
|
|
549
581
|
AIRBYTE_ACTION,
|
|
550
582
|
AIRBYTE_ENTITY_NAME,
|
|
583
|
+
AIRBYTE_STREAM_NAME,
|
|
551
584
|
AIRBYTE_TOKEN_PATH,
|
|
552
585
|
AIRBYTE_BODY_TYPE,
|
|
553
586
|
AIRBYTE_PATH_OVERRIDE,
|
|
@@ -594,6 +627,12 @@ EXTENSION_REGISTRY = {
|
|
|
594
627
|
"required": False,
|
|
595
628
|
"description": "Links schema to an entity/stream",
|
|
596
629
|
},
|
|
630
|
+
AIRBYTE_STREAM_NAME: {
|
|
631
|
+
"location": "schema",
|
|
632
|
+
"type": "string",
|
|
633
|
+
"required": False,
|
|
634
|
+
"description": "Maps entity to Airbyte stream for cache lookup",
|
|
635
|
+
},
|
|
597
636
|
AIRBYTE_TOKEN_PATH: {
|
|
598
637
|
"location": "securityScheme",
|
|
599
638
|
"type": "string",
|
airbyte_agent_salesforce-0.1.25/airbyte_agent_salesforce/_vendored/connector_sdk/introspection.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared introspection utilities for connector metadata.
|
|
3
|
+
|
|
4
|
+
This module provides utilities for introspecting connector metadata,
|
|
5
|
+
generating descriptions, and formatting parameter signatures. These
|
|
6
|
+
functions are used by both the runtime decorators and the generated
|
|
7
|
+
connector code.
|
|
8
|
+
|
|
9
|
+
The module is designed to work with any object conforming to the
|
|
10
|
+
ConnectorModel and EndpointDefinition interfaces from connector_sdk.types.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Any, Protocol
|
|
16
|
+
|
|
17
|
+
# Constants
|
|
18
|
+
MAX_EXAMPLE_QUESTIONS = 5 # Maximum number of example questions to include in description
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EndpointProtocol(Protocol):
|
|
22
|
+
"""Protocol defining the expected interface for endpoint parameters.
|
|
23
|
+
|
|
24
|
+
This allows functions to work with any endpoint-like object
|
|
25
|
+
that has these attributes, including EndpointDefinition and mock objects.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
path_params: list[str]
|
|
29
|
+
path_params_schema: dict[str, dict[str, Any]]
|
|
30
|
+
query_params: list[str]
|
|
31
|
+
query_params_schema: dict[str, dict[str, Any]]
|
|
32
|
+
body_fields: list[str]
|
|
33
|
+
request_schema: dict[str, Any] | None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class EntityProtocol(Protocol):
|
|
37
|
+
"""Protocol defining the expected interface for entity definitions."""
|
|
38
|
+
|
|
39
|
+
name: str
|
|
40
|
+
actions: list[Any]
|
|
41
|
+
endpoints: dict[Any, EndpointProtocol]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ConnectorModelProtocol(Protocol):
|
|
45
|
+
"""Protocol defining the expected interface for connector model parameters.
|
|
46
|
+
|
|
47
|
+
This allows functions to work with any connector-like object
|
|
48
|
+
that has these attributes, including ConnectorModel and mock objects.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def entities(self) -> list[EntityProtocol]: ...
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def openapi_spec(self) -> Any: ...
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def format_param_signature(endpoint: EndpointProtocol) -> str:
|
|
59
|
+
"""Format parameter signature for an endpoint action.
|
|
60
|
+
|
|
61
|
+
Returns a string like: (id*) or (limit?, starting_after?, email?)
|
|
62
|
+
where * = required, ? = optional
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
endpoint: Object conforming to EndpointProtocol (e.g., EndpointDefinition)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Formatted parameter signature string
|
|
69
|
+
"""
|
|
70
|
+
params = []
|
|
71
|
+
|
|
72
|
+
# Defensive: safely access attributes with defaults for malformed endpoints
|
|
73
|
+
path_params = getattr(endpoint, "path_params", []) or []
|
|
74
|
+
query_params = getattr(endpoint, "query_params", []) or []
|
|
75
|
+
query_params_schema = getattr(endpoint, "query_params_schema", {}) or {}
|
|
76
|
+
body_fields = getattr(endpoint, "body_fields", []) or []
|
|
77
|
+
request_schema = getattr(endpoint, "request_schema", None)
|
|
78
|
+
|
|
79
|
+
# Path params (always required)
|
|
80
|
+
for name in path_params:
|
|
81
|
+
params.append(f"{name}*")
|
|
82
|
+
|
|
83
|
+
# Query params
|
|
84
|
+
for name in query_params:
|
|
85
|
+
schema = query_params_schema.get(name, {})
|
|
86
|
+
required = schema.get("required", False)
|
|
87
|
+
params.append(f"{name}{'*' if required else '?'}")
|
|
88
|
+
|
|
89
|
+
# Body fields
|
|
90
|
+
if request_schema:
|
|
91
|
+
required_fields = set(request_schema.get("required", []))
|
|
92
|
+
for name in body_fields:
|
|
93
|
+
params.append(f"{name}{'*' if name in required_fields else '?'}")
|
|
94
|
+
|
|
95
|
+
return f"({', '.join(params)})" if params else "()"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def describe_entities(model: ConnectorModelProtocol) -> list[dict[str, Any]]:
|
|
99
|
+
"""Generate entity descriptions from ConnectorModel.
|
|
100
|
+
|
|
101
|
+
Returns a list of entity descriptions with detailed parameter information
|
|
102
|
+
for each action. This is used by generated connectors' describe() method.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
model: Object conforming to ConnectorModelProtocol (e.g., ConnectorModel)
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
List of entity description dicts with keys:
|
|
109
|
+
- entity_name: Name of the entity (e.g., "contacts", "deals")
|
|
110
|
+
- description: Entity description from the first endpoint
|
|
111
|
+
- available_actions: List of actions (e.g., ["list", "get", "create"])
|
|
112
|
+
- parameters: Dict mapping action -> list of parameter dicts
|
|
113
|
+
"""
|
|
114
|
+
entities = []
|
|
115
|
+
for entity_def in model.entities:
|
|
116
|
+
description = ""
|
|
117
|
+
parameters: dict[str, list[dict[str, Any]]] = {}
|
|
118
|
+
|
|
119
|
+
endpoints = getattr(entity_def, "endpoints", {}) or {}
|
|
120
|
+
if endpoints:
|
|
121
|
+
for action, endpoint in endpoints.items():
|
|
122
|
+
# Get description from first endpoint that has one
|
|
123
|
+
if not description:
|
|
124
|
+
endpoint_desc = getattr(endpoint, "description", None)
|
|
125
|
+
if endpoint_desc:
|
|
126
|
+
description = endpoint_desc
|
|
127
|
+
|
|
128
|
+
action_params: list[dict[str, Any]] = []
|
|
129
|
+
|
|
130
|
+
# Defensive: safely access endpoint attributes
|
|
131
|
+
path_params = getattr(endpoint, "path_params", []) or []
|
|
132
|
+
path_params_schema = getattr(endpoint, "path_params_schema", {}) or {}
|
|
133
|
+
query_params = getattr(endpoint, "query_params", []) or []
|
|
134
|
+
query_params_schema = getattr(endpoint, "query_params_schema", {}) or {}
|
|
135
|
+
body_fields = getattr(endpoint, "body_fields", []) or []
|
|
136
|
+
request_schema = getattr(endpoint, "request_schema", None)
|
|
137
|
+
|
|
138
|
+
# Path params (always required)
|
|
139
|
+
for param_name in path_params:
|
|
140
|
+
schema = path_params_schema.get(param_name, {})
|
|
141
|
+
action_params.append(
|
|
142
|
+
{
|
|
143
|
+
"name": param_name,
|
|
144
|
+
"in": "path",
|
|
145
|
+
"required": True,
|
|
146
|
+
"type": schema.get("type", "string"),
|
|
147
|
+
"description": schema.get("description", ""),
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Query params
|
|
152
|
+
for param_name in query_params:
|
|
153
|
+
schema = query_params_schema.get(param_name, {})
|
|
154
|
+
action_params.append(
|
|
155
|
+
{
|
|
156
|
+
"name": param_name,
|
|
157
|
+
"in": "query",
|
|
158
|
+
"required": schema.get("required", False),
|
|
159
|
+
"type": schema.get("type", "string"),
|
|
160
|
+
"description": schema.get("description", ""),
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Body fields
|
|
165
|
+
if request_schema:
|
|
166
|
+
required_fields = request_schema.get("required", [])
|
|
167
|
+
properties = request_schema.get("properties", {})
|
|
168
|
+
for param_name in body_fields:
|
|
169
|
+
prop = properties.get(param_name, {})
|
|
170
|
+
action_params.append(
|
|
171
|
+
{
|
|
172
|
+
"name": param_name,
|
|
173
|
+
"in": "body",
|
|
174
|
+
"required": param_name in required_fields,
|
|
175
|
+
"type": prop.get("type", "string"),
|
|
176
|
+
"description": prop.get("description", ""),
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if action_params:
|
|
181
|
+
# Action is an enum, use .value to get string
|
|
182
|
+
action_key = action.value if hasattr(action, "value") else str(action)
|
|
183
|
+
parameters[action_key] = action_params
|
|
184
|
+
|
|
185
|
+
actions = getattr(entity_def, "actions", []) or []
|
|
186
|
+
entities.append(
|
|
187
|
+
{
|
|
188
|
+
"entity_name": entity_def.name,
|
|
189
|
+
"description": description,
|
|
190
|
+
"available_actions": [a.value if hasattr(a, "value") else str(a) for a in actions],
|
|
191
|
+
"parameters": parameters,
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return entities
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def generate_tool_description(model: ConnectorModelProtocol) -> str:
|
|
199
|
+
"""Generate AI tool description from connector metadata.
|
|
200
|
+
|
|
201
|
+
Produces a detailed description that includes:
|
|
202
|
+
- Per-entity/action parameter signatures with required (*) and optional (?) markers
|
|
203
|
+
- Response structure documentation with pagination hints
|
|
204
|
+
- Example questions if available in the OpenAPI spec
|
|
205
|
+
|
|
206
|
+
This is used by the Connector.describe class method decorator to populate
|
|
207
|
+
function docstrings for AI framework integration.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
model: Object conforming to ConnectorModelProtocol (e.g., ConnectorModel)
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Formatted description string suitable for AI tool documentation
|
|
214
|
+
"""
|
|
215
|
+
lines = []
|
|
216
|
+
|
|
217
|
+
# Entity/action parameter details (including pagination params like limit, starting_after)
|
|
218
|
+
lines.append("ENTITIES AND PARAMETERS:")
|
|
219
|
+
for entity in model.entities:
|
|
220
|
+
lines.append(f" {entity.name}:")
|
|
221
|
+
actions = getattr(entity, "actions", []) or []
|
|
222
|
+
endpoints = getattr(entity, "endpoints", {}) or {}
|
|
223
|
+
for action in actions:
|
|
224
|
+
action_str = action.value if hasattr(action, "value") else str(action)
|
|
225
|
+
endpoint = endpoints.get(action)
|
|
226
|
+
if endpoint:
|
|
227
|
+
param_sig = format_param_signature(endpoint)
|
|
228
|
+
lines.append(f" - {action_str}{param_sig}")
|
|
229
|
+
else:
|
|
230
|
+
lines.append(f" - {action_str}()")
|
|
231
|
+
|
|
232
|
+
# Response structure (brief, includes pagination hint)
|
|
233
|
+
lines.append("")
|
|
234
|
+
lines.append("RESPONSE STRUCTURE:")
|
|
235
|
+
lines.append(" - list/search: {data: [...], meta: {has_more: bool}}")
|
|
236
|
+
lines.append(" - get: Returns entity directly (no envelope)")
|
|
237
|
+
lines.append(" To paginate: pass starting_after=<last_id> while has_more is true")
|
|
238
|
+
|
|
239
|
+
# Add example questions if available in openapi_spec
|
|
240
|
+
openapi_spec = getattr(model, "openapi_spec", None)
|
|
241
|
+
if openapi_spec:
|
|
242
|
+
info = getattr(openapi_spec, "info", None)
|
|
243
|
+
if info:
|
|
244
|
+
example_questions = getattr(info, "x_airbyte_example_questions", None)
|
|
245
|
+
if example_questions:
|
|
246
|
+
supported = getattr(example_questions, "supported", None)
|
|
247
|
+
if supported:
|
|
248
|
+
lines.append("")
|
|
249
|
+
lines.append("EXAMPLE QUESTIONS:")
|
|
250
|
+
for q in supported[:MAX_EXAMPLE_QUESTIONS]:
|
|
251
|
+
lines.append(f" - {q}")
|
|
252
|
+
|
|
253
|
+
# Generic parameter description for function signature
|
|
254
|
+
lines.append("")
|
|
255
|
+
lines.append("FUNCTION PARAMETERS:")
|
|
256
|
+
lines.append(" - entity: Entity name (string)")
|
|
257
|
+
lines.append(" - action: Operation to perform (string)")
|
|
258
|
+
lines.append(" - params: Operation parameters (dict) - see entity details above")
|
|
259
|
+
lines.append("")
|
|
260
|
+
lines.append("Parameter markers: * = required, ? = optional")
|
|
261
|
+
|
|
262
|
+
return "\n".join(lines)
|
|
@@ -65,8 +65,9 @@ class Schema(BaseModel):
|
|
|
65
65
|
write_only: Optional[bool] = Field(None, alias="writeOnly")
|
|
66
66
|
deprecated: Optional[bool] = None
|
|
67
67
|
|
|
68
|
-
# Airbyte
|
|
68
|
+
# Airbyte extensions
|
|
69
69
|
x_airbyte_entity_name: Optional[str] = Field(None, alias="x-airbyte-entity-name")
|
|
70
|
+
x_airbyte_stream_name: Optional[str] = Field(None, alias="x-airbyte-stream-name")
|
|
70
71
|
|
|
71
72
|
|
|
72
73
|
class Parameter(BaseModel):
|
|
@@ -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: Optional[Dict[str, str]] = 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):
|
|
@@ -99,6 +103,12 @@ class AirbyteAuthConfig(BaseModel):
|
|
|
99
103
|
properties: Optional[Dict[str, AuthConfigFieldSpec]] = None
|
|
100
104
|
auth_mapping: Optional[Dict[str, str]] = None
|
|
101
105
|
|
|
106
|
+
# Replication connector auth mapping
|
|
107
|
+
replication_auth_key_mapping: Optional[Dict[str, str]] = Field(
|
|
108
|
+
None,
|
|
109
|
+
description="Mapping from source config paths (e.g., 'credentials.api_key') to auth config keys for direct connectors",
|
|
110
|
+
)
|
|
111
|
+
|
|
102
112
|
# Multiple options (oneOf)
|
|
103
113
|
one_of: Optional[List[AuthConfigOption]] = Field(None, alias="oneOf")
|
|
104
114
|
|
|
@@ -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")
|
|
@@ -4,14 +4,15 @@ salesforce connector.
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
import logging
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Callable, TypeVar, AsyncIterator, overload
|
|
8
9
|
try:
|
|
9
10
|
from typing import Literal
|
|
10
11
|
except ImportError:
|
|
11
12
|
from typing_extensions import Literal
|
|
12
13
|
|
|
13
14
|
from .connector_model import SalesforceConnectorModel
|
|
14
|
-
|
|
15
|
+
from ._vendored.connector_sdk.introspection import describe_entities, generate_tool_description
|
|
15
16
|
from .types import (
|
|
16
17
|
AccountsGetParams,
|
|
17
18
|
AccountsListParams,
|
|
@@ -48,7 +49,6 @@ from .types import (
|
|
|
48
49
|
TasksListParams,
|
|
49
50
|
TasksSearchParams,
|
|
50
51
|
)
|
|
51
|
-
|
|
52
52
|
if TYPE_CHECKING:
|
|
53
53
|
from .models import SalesforceAuthConfig
|
|
54
54
|
# Import response models and envelope models at runtime
|
|
@@ -81,6 +81,9 @@ from .models import (
|
|
|
81
81
|
TaskQueryResult,
|
|
82
82
|
)
|
|
83
83
|
|
|
84
|
+
# TypeVar for decorator type preservation
|
|
85
|
+
_F = TypeVar("_F", bound=Callable[..., Any])
|
|
86
|
+
|
|
84
87
|
|
|
85
88
|
class SalesforceConnector:
|
|
86
89
|
"""
|
|
@@ -90,7 +93,7 @@ class SalesforceConnector:
|
|
|
90
93
|
"""
|
|
91
94
|
|
|
92
95
|
connector_name = "salesforce"
|
|
93
|
-
connector_version = "1.0.
|
|
96
|
+
connector_version = "1.0.4"
|
|
94
97
|
vendored_sdk_version = "0.1.0" # Version of vendored connector-sdk
|
|
95
98
|
|
|
96
99
|
# Map of (entity, action) -> has_extractors for envelope wrapping decision
|
|
@@ -613,6 +616,88 @@ class SalesforceConnector:
|
|
|
613
616
|
# No extractors - return raw response data
|
|
614
617
|
return result.data
|
|
615
618
|
|
|
619
|
+
# ===== INTROSPECTION METHODS =====
|
|
620
|
+
|
|
621
|
+
@classmethod
|
|
622
|
+
def describe(cls, func: _F) -> _F:
|
|
623
|
+
"""
|
|
624
|
+
Decorator that populates a function's docstring with connector capabilities.
|
|
625
|
+
|
|
626
|
+
This class method can be used as a decorator to automatically generate
|
|
627
|
+
comprehensive documentation for AI tool functions.
|
|
628
|
+
|
|
629
|
+
Usage:
|
|
630
|
+
@mcp.tool()
|
|
631
|
+
@SalesforceConnector.describe
|
|
632
|
+
async def execute(entity: str, action: str, params: dict):
|
|
633
|
+
'''Execute operations.'''
|
|
634
|
+
...
|
|
635
|
+
|
|
636
|
+
The decorated function's __doc__ will be updated with:
|
|
637
|
+
- Available entities and their actions
|
|
638
|
+
- Parameter signatures with required (*) and optional (?) markers
|
|
639
|
+
- Response structure documentation
|
|
640
|
+
- Example questions (if available in OpenAPI spec)
|
|
641
|
+
|
|
642
|
+
Args:
|
|
643
|
+
func: The function to decorate
|
|
644
|
+
|
|
645
|
+
Returns:
|
|
646
|
+
The same function with updated __doc__
|
|
647
|
+
"""
|
|
648
|
+
description = generate_tool_description(SalesforceConnectorModel)
|
|
649
|
+
|
|
650
|
+
original_doc = func.__doc__ or ""
|
|
651
|
+
if original_doc.strip():
|
|
652
|
+
func.__doc__ = f"{original_doc.strip()}\n\n{description}"
|
|
653
|
+
else:
|
|
654
|
+
func.__doc__ = description
|
|
655
|
+
|
|
656
|
+
return func
|
|
657
|
+
|
|
658
|
+
def list_entities(self) -> list[dict[str, Any]]:
|
|
659
|
+
"""
|
|
660
|
+
Get structured data about available entities, actions, and parameters.
|
|
661
|
+
|
|
662
|
+
Returns a list of entity descriptions with:
|
|
663
|
+
- entity_name: Name of the entity (e.g., "contacts", "deals")
|
|
664
|
+
- description: Entity description from the first endpoint
|
|
665
|
+
- available_actions: List of actions (e.g., ["list", "get", "create"])
|
|
666
|
+
- parameters: Dict mapping action -> list of parameter dicts
|
|
667
|
+
|
|
668
|
+
Example:
|
|
669
|
+
entities = connector.list_entities()
|
|
670
|
+
for entity in entities:
|
|
671
|
+
print(f"{entity['entity_name']}: {entity['available_actions']}")
|
|
672
|
+
"""
|
|
673
|
+
return describe_entities(SalesforceConnectorModel)
|
|
674
|
+
|
|
675
|
+
def entity_schema(self, entity: str) -> dict[str, Any] | None:
|
|
676
|
+
"""
|
|
677
|
+
Get the JSON schema for an entity.
|
|
678
|
+
|
|
679
|
+
Args:
|
|
680
|
+
entity: Entity name (e.g., "contacts", "companies")
|
|
681
|
+
|
|
682
|
+
Returns:
|
|
683
|
+
JSON schema dict describing the entity structure, or None if not found.
|
|
684
|
+
|
|
685
|
+
Example:
|
|
686
|
+
schema = connector.entity_schema("contacts")
|
|
687
|
+
if schema:
|
|
688
|
+
print(f"Contact properties: {list(schema.get('properties', {}).keys())}")
|
|
689
|
+
"""
|
|
690
|
+
entity_def = next(
|
|
691
|
+
(e for e in SalesforceConnectorModel.entities if e.name == entity),
|
|
692
|
+
None
|
|
693
|
+
)
|
|
694
|
+
if entity_def is None:
|
|
695
|
+
logging.getLogger(__name__).warning(
|
|
696
|
+
f"Entity '{entity}' not found. Available entities: "
|
|
697
|
+
f"{[e.name for e in SalesforceConnectorModel.entities]}"
|
|
698
|
+
)
|
|
699
|
+
return entity_def.entity_schema if entity_def else None
|
|
700
|
+
|
|
616
701
|
|
|
617
702
|
|
|
618
703
|
class AccountsQuery:
|
|
@@ -29,7 +29,7 @@ from uuid import (
|
|
|
29
29
|
SalesforceConnectorModel: ConnectorModel = ConnectorModel(
|
|
30
30
|
id=UUID('b117307c-14b6-41aa-9422-947e34922962'),
|
|
31
31
|
name='salesforce',
|
|
32
|
-
version='1.0.
|
|
32
|
+
version='1.0.4',
|
|
33
33
|
base_url='{instance_url}/services/data/v59.0',
|
|
34
34
|
auth=AuthConfig(
|
|
35
35
|
type=AuthType.OAUTH2,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|