airbyte-agent-github 0.18.128__tar.gz → 0.18.129__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_github-0.18.128 → airbyte_agent_github-0.18.129}/CHANGELOG.md +5 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/PKG-INFO +3 -3
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/README.md +2 -2
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/connector_model_loader.py +35 -6
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/executor/local_executor.py +59 -19
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/extensions.py +74 -42
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/introspection.py +56 -25
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/schema/__init__.py +3 -1
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/schema/base.py +7 -1
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/schema/components.py +19 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/schema/extensions.py +72 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/schema/operations.py +0 -8
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/types.py +14 -9
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/validation/readiness.py +54 -16
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/connector.py +1 -1
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/connector_model.py +26 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/pyproject.toml +1 -1
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/.gitignore +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/AUTH.md +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/REFERENCE.md +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/__init__.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/__init__.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/__init__.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/auth_strategies.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/auth_template.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/cloud_utils/__init__.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/cloud_utils/client.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/constants.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/exceptions.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/executor/__init__.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/executor/hosted_executor.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/executor/models.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/http/__init__.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/http/adapters/__init__.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/http/adapters/httpx_adapter.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/http/config.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/http/exceptions.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/http/protocols.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/http/response.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/http_client.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/logging/__init__.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/logging/logger.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/logging/types.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/observability/__init__.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/observability/config.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/observability/models.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/observability/redactor.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/observability/session.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/performance/__init__.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/performance/instrumentation.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/performance/metrics.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/schema/connector.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/schema/security.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/secrets.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/telemetry/__init__.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/telemetry/config.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/telemetry/events.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/telemetry/tracker.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/utils.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/validation/__init__.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/validation/cache.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/validation/manifest.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/validation/models.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/validation/overview.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/validation/replication.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/models.py +0 -0
- {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/types.py +0 -0
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Github changelog
|
|
2
2
|
|
|
3
|
+
## [0.18.129] - 2026-04-07
|
|
4
|
+
- Updated connector definition (YAML version 0.1.17)
|
|
5
|
+
- Source commit: fbc0bacd
|
|
6
|
+
- SDK version: 0.1.0
|
|
7
|
+
|
|
3
8
|
## [0.18.128] - 2026-04-01
|
|
4
9
|
- Updated connector definition (YAML version 0.1.17)
|
|
5
10
|
- Source commit: e4c74933
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: airbyte-agent-github
|
|
3
|
-
Version: 0.18.
|
|
3
|
+
Version: 0.18.129
|
|
4
4
|
Summary: Airbyte Github 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/
|
|
@@ -167,7 +167,7 @@ See the official [Github API reference](https://docs.github.com/en/rest).
|
|
|
167
167
|
|
|
168
168
|
## Version information
|
|
169
169
|
|
|
170
|
-
- **Package version:** 0.18.
|
|
170
|
+
- **Package version:** 0.18.129
|
|
171
171
|
- **Connector version:** 0.1.17
|
|
172
|
-
- **Generated with Connector SDK commit SHA:**
|
|
172
|
+
- **Generated with Connector SDK commit SHA:** fbc0bacdabf9b1de67621333cd97edee9c444de4
|
|
173
173
|
- **Changelog:** [View changelog](https://github.com/airbytehq/airbyte-agent-connectors/blob/main/connectors/github/CHANGELOG.md)
|
|
@@ -134,7 +134,7 @@ See the official [Github API reference](https://docs.github.com/en/rest).
|
|
|
134
134
|
|
|
135
135
|
## Version information
|
|
136
136
|
|
|
137
|
-
- **Package version:** 0.18.
|
|
137
|
+
- **Package version:** 0.18.129
|
|
138
138
|
- **Connector version:** 0.1.17
|
|
139
|
-
- **Generated with Connector SDK commit SHA:**
|
|
139
|
+
- **Generated with Connector SDK commit SHA:** fbc0bacdabf9b1de67621333cd97edee9c444de4
|
|
140
140
|
- **Changelog:** [View changelog](https://github.com/airbytehq/airbyte-agent-connectors/blob/main/connectors/github/CHANGELOG.md)
|
|
@@ -412,7 +412,6 @@ def convert_openapi_to_connector_model(spec: OpenAPIConnector) -> ConnectorModel
|
|
|
412
412
|
path_override = operation.x_airbyte_path_override
|
|
413
413
|
record_extractor = operation.x_airbyte_record_extractor
|
|
414
414
|
meta_extractor = operation.x_airbyte_meta_extractor
|
|
415
|
-
param_sources = operation.x_airbyte_param_sources or {}
|
|
416
415
|
|
|
417
416
|
if not entity_name:
|
|
418
417
|
raise InvalidOpenAPIError(
|
|
@@ -531,7 +530,6 @@ def convert_openapi_to_connector_model(spec: OpenAPIConnector) -> ConnectorModel
|
|
|
531
530
|
file_field=file_field,
|
|
532
531
|
untested=untested,
|
|
533
532
|
preferred_for_check=preferred_for_check,
|
|
534
|
-
param_sources=param_sources,
|
|
535
533
|
upload_file_param=upload_file_param,
|
|
536
534
|
no_content_response=has_no_content_response,
|
|
537
535
|
)
|
|
@@ -550,15 +548,18 @@ def convert_openapi_to_connector_model(spec: OpenAPIConnector) -> ConnectorModel
|
|
|
550
548
|
for entity_name, endpoints_dict in entities_map.items():
|
|
551
549
|
actions = list(endpoints_dict.keys())
|
|
552
550
|
|
|
553
|
-
# Get schema and
|
|
551
|
+
# Get schema, stream_name, and ai_hints from components if available
|
|
554
552
|
schema = None
|
|
555
553
|
entity_stream_name = None
|
|
554
|
+
entity_ai_hints = None
|
|
556
555
|
if spec.components:
|
|
557
556
|
# Look for a schema matching the entity name
|
|
558
557
|
for schema_name, schema_def in spec.components.schemas.items():
|
|
559
558
|
if schema_def.x_airbyte_entity_name == entity_name or schema_name.lower() == entity_name.lower():
|
|
560
559
|
schema = schema_def.model_dump(by_alias=True)
|
|
561
560
|
entity_stream_name = schema_def.x_airbyte_stream_name
|
|
561
|
+
if schema_def.x_airbyte_ai_hints is not None:
|
|
562
|
+
entity_ai_hints = schema_def.x_airbyte_ai_hints.model_dump(by_alias=True)
|
|
562
563
|
break
|
|
563
564
|
|
|
564
565
|
entity = EntityDefinition(
|
|
@@ -567,9 +568,16 @@ def convert_openapi_to_connector_model(spec: OpenAPIConnector) -> ConnectorModel
|
|
|
567
568
|
actions=actions,
|
|
568
569
|
endpoints=endpoints_dict,
|
|
569
570
|
schema=schema,
|
|
571
|
+
ai_hints=entity_ai_hints,
|
|
570
572
|
)
|
|
571
573
|
entities.append(entity)
|
|
572
574
|
|
|
575
|
+
# Attach entity relationships from spec
|
|
576
|
+
_attach_entity_relationships(entities, spec)
|
|
577
|
+
|
|
578
|
+
# Read scoping config from spec
|
|
579
|
+
scoping = list(spec.info.x_airbyte_scoping)
|
|
580
|
+
|
|
573
581
|
# Extract retry config from x-airbyte-retry-config extension
|
|
574
582
|
retry_config = spec.info.x_airbyte_retry_config
|
|
575
583
|
connector_definition_id = spec.info.x_airbyte_connector_definition_id
|
|
@@ -578,6 +586,9 @@ def convert_openapi_to_connector_model(spec: OpenAPIConnector) -> ConnectorModel
|
|
|
578
586
|
|
|
579
587
|
search_field_paths = _extract_search_field_paths(spec)
|
|
580
588
|
|
|
589
|
+
# Extract example questions from spec (serialized separately from openapi_spec)
|
|
590
|
+
example_questions = getattr(spec.info, "x_airbyte_example_questions", None)
|
|
591
|
+
|
|
581
592
|
# Create ConnectorModel
|
|
582
593
|
model = ConnectorModel(
|
|
583
594
|
id=connector_definition_id,
|
|
@@ -589,12 +600,32 @@ def convert_openapi_to_connector_model(spec: OpenAPIConnector) -> ConnectorModel
|
|
|
589
600
|
openapi_spec=spec,
|
|
590
601
|
retry_config=retry_config,
|
|
591
602
|
search_field_paths=search_field_paths,
|
|
603
|
+
example_questions=example_questions,
|
|
592
604
|
server_variable_defaults=server_variable_defaults,
|
|
605
|
+
scoping=scoping,
|
|
593
606
|
)
|
|
594
607
|
|
|
595
608
|
return model
|
|
596
609
|
|
|
597
610
|
|
|
611
|
+
def _attach_entity_relationships(
|
|
612
|
+
entities: list[EntityDefinition],
|
|
613
|
+
spec: OpenAPIConnector,
|
|
614
|
+
) -> None:
|
|
615
|
+
"""Attach relationships to EntityDefinitions from x-airbyte-entity-relationships.
|
|
616
|
+
|
|
617
|
+
Reads the connector-wide relationship declarations from the spec's info
|
|
618
|
+
object and attaches them to the matching EntityDefinition by source_entity name.
|
|
619
|
+
"""
|
|
620
|
+
entity_relationships = spec.info.x_airbyte_entity_relationships
|
|
621
|
+
if entity_relationships:
|
|
622
|
+
entity_map = {e.name: e for e in entities}
|
|
623
|
+
for rel in entity_relationships:
|
|
624
|
+
entity = entity_map.get(rel.source_entity)
|
|
625
|
+
if entity is not None:
|
|
626
|
+
entity.relationships.append(rel)
|
|
627
|
+
|
|
628
|
+
|
|
598
629
|
def _get_attribute_flexible(obj: Any, *names: str) -> Any:
|
|
599
630
|
"""Get attribute from object, trying multiple name variants.
|
|
600
631
|
|
|
@@ -1063,9 +1094,7 @@ def load_connector_model(definition_path: str | Path) -> ConnectorModel:
|
|
|
1063
1094
|
raise ValueError("Invalid connector.yaml: empty file")
|
|
1064
1095
|
|
|
1065
1096
|
if "openapi" not in raw_definition:
|
|
1066
|
-
raise ValueError(
|
|
1067
|
-
"Invalid connector.yaml: missing 'openapi' key. Only OpenAPI 3.1 format is supported."
|
|
1068
|
-
)
|
|
1097
|
+
raise ValueError("Invalid connector.yaml: missing 'openapi' key. Only OpenAPI 3.1 format is supported.")
|
|
1069
1098
|
|
|
1070
1099
|
spec = parse_openapi_spec(raw_definition)
|
|
1071
1100
|
return convert_openapi_to_connector_model(spec)
|
|
@@ -254,6 +254,18 @@ class LocalExecutor:
|
|
|
254
254
|
if endpoint:
|
|
255
255
|
self._operation_index[(entity.name, action)] = endpoint
|
|
256
256
|
|
|
257
|
+
# Build O(1) scoping index: param_name -> config_key
|
|
258
|
+
self._scoping_index: dict[str, str] = {s.param: (s.config_key or s.param) for s in self.model.scoping}
|
|
259
|
+
|
|
260
|
+
# Build O(1) global foreign-key index: fk_name -> (target_entity, target_key)
|
|
261
|
+
# Used as a fallback when the current entity has no relationship for a param
|
|
262
|
+
# but another entity in the connector declares one with the same foreign_key.
|
|
263
|
+
self._global_fk_index: dict[str, tuple[str, str]] = {}
|
|
264
|
+
for entity in self.model.entities:
|
|
265
|
+
for rel in entity.relationships:
|
|
266
|
+
if rel.foreign_key not in self._global_fk_index:
|
|
267
|
+
self._global_fk_index[rel.foreign_key] = (rel.target_entity, rel.target_key)
|
|
268
|
+
|
|
257
269
|
# Register operation handlers (order matters for can_handle priority)
|
|
258
270
|
op_context = _OperationContext(self)
|
|
259
271
|
self._operation_handlers: list[_OperationHandler] = [
|
|
@@ -696,21 +708,29 @@ class LocalExecutor:
|
|
|
696
708
|
params = {"limit": 1} if action == Action.LIST else {}
|
|
697
709
|
# Inject query param defaults from schema so that required params
|
|
698
710
|
# with defaults (e.g. Salesforce SOQL `q` parameter) are included
|
|
699
|
-
# in the probe request without needing explicit
|
|
711
|
+
# in the probe request without needing explicit configuration.
|
|
700
712
|
for param_name, schema in endpoint.query_params_schema.items():
|
|
701
713
|
if param_name not in params and schema.get("default") is not None:
|
|
702
714
|
params[param_name] = schema["default"]
|
|
703
|
-
# Collect all params that need resolution: path params
|
|
704
|
-
# query
|
|
705
|
-
# (
|
|
715
|
+
# Collect all params that need resolution: path params, entity-
|
|
716
|
+
# relationship foreign_keys, and query params with matching config
|
|
717
|
+
# keys (so config values can override schema defaults).
|
|
706
718
|
params_needing_resolution = list(endpoint.path_params)
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
719
|
+
entity_def = self._entity_index.get(entity_name)
|
|
720
|
+
if entity_def:
|
|
721
|
+
for rel in entity_def.relationships:
|
|
722
|
+
if rel.foreign_key not in params_needing_resolution:
|
|
723
|
+
params_needing_resolution.append(rel.foreign_key)
|
|
724
|
+
# Also resolve query params that have a matching scoping or config
|
|
725
|
+
# key, so explicit config values take precedence over defaults.
|
|
726
|
+
for qp in endpoint.query_params:
|
|
727
|
+
if qp not in params_needing_resolution and (
|
|
728
|
+
qp in self._scoping_index or qp in self.config_values
|
|
729
|
+
):
|
|
730
|
+
params_needing_resolution.append(qp)
|
|
711
731
|
if params_needing_resolution:
|
|
712
732
|
try:
|
|
713
|
-
resolved = await self.
|
|
733
|
+
resolved = await self._resolve_path_params(
|
|
714
734
|
entity_name,
|
|
715
735
|
endpoint,
|
|
716
736
|
standard_handler,
|
|
@@ -743,7 +763,7 @@ class LocalExecutor:
|
|
|
743
763
|
"checked_action": action.value,
|
|
744
764
|
}
|
|
745
765
|
|
|
746
|
-
async def
|
|
766
|
+
async def _resolve_path_params(
|
|
747
767
|
self,
|
|
748
768
|
entity_name: str,
|
|
749
769
|
endpoint: EndpointDefinition,
|
|
@@ -752,7 +772,12 @@ class LocalExecutor:
|
|
|
752
772
|
depth: int = 0,
|
|
753
773
|
params_to_resolve: list[str] | None = None,
|
|
754
774
|
) -> dict[str, Any]:
|
|
755
|
-
"""Resolve params using
|
|
775
|
+
"""Resolve params using scoping, config fallback, and entity relationships.
|
|
776
|
+
|
|
777
|
+
Resolution priority for each path param:
|
|
778
|
+
1. Scoping index (from ConnectorModel.scoping) -> config_values
|
|
779
|
+
2. Config fallback (param name matches a config_values key)
|
|
780
|
+
3. Entity relationships (from entity.relationships by foreign_key)
|
|
756
781
|
|
|
757
782
|
Returns dict of {param_name: resolved_value}.
|
|
758
783
|
Raises ParamResolutionError if any param cannot be resolved.
|
|
@@ -760,20 +785,35 @@ class LocalExecutor:
|
|
|
760
785
|
if depth > MAX_PARAM_RESOLUTION_DEPTH:
|
|
761
786
|
raise ParamResolutionError(f"Max resolution depth exceeded for '{entity_name}'")
|
|
762
787
|
|
|
788
|
+
# Build relationship index for this entity: foreign_key -> (target_entity, target_key)
|
|
789
|
+
entity_def = self._entity_index.get(entity_name)
|
|
790
|
+
rel_index: dict[str, tuple[str, str]] = {}
|
|
791
|
+
if entity_def:
|
|
792
|
+
for rel in entity_def.relationships:
|
|
793
|
+
rel_index[rel.foreign_key] = (rel.target_entity, rel.target_key)
|
|
794
|
+
|
|
763
795
|
target_params = params_to_resolve if params_to_resolve is not None else list(endpoint.path_params)
|
|
764
796
|
|
|
765
797
|
resolved: dict[str, Any] = {}
|
|
766
798
|
for param_name in target_params:
|
|
767
|
-
|
|
799
|
+
# 1. Check scoping index
|
|
800
|
+
scoping_key = self._scoping_index.get(param_name)
|
|
801
|
+
if scoping_key and scoping_key in self.config_values:
|
|
802
|
+
resolved[param_name] = self.config_values[scoping_key]
|
|
803
|
+
continue
|
|
768
804
|
|
|
769
|
-
|
|
770
|
-
if
|
|
771
|
-
resolved[param_name] = self.config_values[
|
|
805
|
+
# 2. Config fallback: param name matches a config_values key
|
|
806
|
+
if param_name in self.config_values:
|
|
807
|
+
resolved[param_name] = self.config_values[param_name]
|
|
772
808
|
continue
|
|
773
809
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
810
|
+
# 3. Check entity relationships by foreign_key
|
|
811
|
+
if param_name in rel_index:
|
|
812
|
+
parent_entity_name, parent_key = rel_index[param_name]
|
|
813
|
+
elif param_name in self._global_fk_index:
|
|
814
|
+
# Fallback: another entity declares a relationship for this foreign key
|
|
815
|
+
parent_entity_name, parent_key = self._global_fk_index[param_name]
|
|
816
|
+
else:
|
|
777
817
|
raise ParamResolutionError(f"Cannot resolve param '{param_name}' for entity '{entity_name}'")
|
|
778
818
|
if parent_entity_name == entity_name:
|
|
779
819
|
raise ParamResolutionError(f"Self-referential param '{param_name}' on entity '{entity_name}'")
|
|
@@ -788,7 +828,7 @@ class LocalExecutor:
|
|
|
788
828
|
if pname not in parent_params and pschema.get("default") is not None:
|
|
789
829
|
parent_params[pname] = pschema["default"]
|
|
790
830
|
if parent_endpoint.path_params:
|
|
791
|
-
parent_resolved = await self.
|
|
831
|
+
parent_resolved = await self._resolve_path_params(
|
|
792
832
|
parent_entity_name,
|
|
793
833
|
parent_endpoint,
|
|
794
834
|
standard_handler,
|
|
@@ -450,41 +450,6 @@ Example:
|
|
|
450
450
|
```
|
|
451
451
|
"""
|
|
452
452
|
|
|
453
|
-
AIRBYTE_PARAM_SOURCES = "x-airbyte-param-sources"
|
|
454
|
-
"""
|
|
455
|
-
Extension: x-airbyte-param-sources
|
|
456
|
-
Location: Operation object (on individual HTTP operations)
|
|
457
|
-
Type: dict[str, dict[str, str]] (param name → source declaration)
|
|
458
|
-
Required: No
|
|
459
|
-
|
|
460
|
-
Description:
|
|
461
|
-
Per-param source declarations for entity dependency resolution. Each key is
|
|
462
|
-
a parameter name; each value is a dict describing where that parameter's
|
|
463
|
-
value comes from at runtime.
|
|
464
|
-
|
|
465
|
-
Two source patterns are supported:
|
|
466
|
-
|
|
467
|
-
Pattern 1 — Parent entity (param value comes from another entity's field):
|
|
468
|
-
parent_entity: Name of the parent entity
|
|
469
|
-
parent_key: Field on the parent entity that supplies the value
|
|
470
|
-
|
|
471
|
-
Pattern 2 — Config (param value comes from user-provided configuration):
|
|
472
|
-
config: Name of the config key
|
|
473
|
-
|
|
474
|
-
Example:
|
|
475
|
-
```yaml
|
|
476
|
-
paths:
|
|
477
|
-
/accounts/{account_id}/contacts:
|
|
478
|
-
get:
|
|
479
|
-
x-airbyte-entity: contacts
|
|
480
|
-
x-airbyte-action: list
|
|
481
|
-
x-airbyte-param-sources:
|
|
482
|
-
account_id:
|
|
483
|
-
parent_entity: accounts
|
|
484
|
-
parent_key: id
|
|
485
|
-
```
|
|
486
|
-
"""
|
|
487
|
-
|
|
488
453
|
AIRBYTE_TOKEN_EXTRACT = "x-airbyte-token-extract"
|
|
489
454
|
"""
|
|
490
455
|
Extension: x-airbyte-token-extract
|
|
@@ -534,6 +499,66 @@ Example:
|
|
|
534
499
|
```
|
|
535
500
|
"""
|
|
536
501
|
|
|
502
|
+
AIRBYTE_ENTITY_RELATIONSHIPS = "x-airbyte-entity-relationships"
|
|
503
|
+
"""
|
|
504
|
+
Extension: x-airbyte-entity-relationships
|
|
505
|
+
Location: OpenAPI Info object
|
|
506
|
+
Type: list[EntityRelationshipConfig]
|
|
507
|
+
Required: No
|
|
508
|
+
|
|
509
|
+
Description:
|
|
510
|
+
Declares foreign-key relationships between entities. Each entry specifies
|
|
511
|
+
which entity holds the foreign key (source_entity), which entity is
|
|
512
|
+
referenced (target_entity), the foreign key field, and optional cardinality.
|
|
513
|
+
|
|
514
|
+
Provides a single, connector-wide relationship graph that the runtime
|
|
515
|
+
uses for dependency resolution and agent introspection.
|
|
516
|
+
|
|
517
|
+
Example:
|
|
518
|
+
```yaml
|
|
519
|
+
info:
|
|
520
|
+
title: My API
|
|
521
|
+
x-airbyte-entity-relationships:
|
|
522
|
+
- source_entity: contacts
|
|
523
|
+
target_entity: accounts
|
|
524
|
+
foreign_key: account_id
|
|
525
|
+
cardinality: many_to_one
|
|
526
|
+
description: "Contact belongs to an account"
|
|
527
|
+
- source_entity: deals
|
|
528
|
+
target_entity: contacts
|
|
529
|
+
foreign_key: contact_id
|
|
530
|
+
target_key: id
|
|
531
|
+
cardinality: many_to_one
|
|
532
|
+
```
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
AIRBYTE_SCOPING = "x-airbyte-scoping"
|
|
536
|
+
"""
|
|
537
|
+
Extension: x-airbyte-scoping
|
|
538
|
+
Location: OpenAPI Info object
|
|
539
|
+
Type: list[ScopingParamConfig]
|
|
540
|
+
Required: No
|
|
541
|
+
|
|
542
|
+
Description:
|
|
543
|
+
Declares path parameters that should be resolved from the connector's
|
|
544
|
+
config values at runtime, rather than being supplied per-request. This
|
|
545
|
+
is used for tenant-scoped APIs where a path segment (e.g., account_id)
|
|
546
|
+
is fixed for the lifetime of the connector instance.
|
|
547
|
+
|
|
548
|
+
Each entry maps a path parameter name to an optional config key. If
|
|
549
|
+
config_key is omitted, the param name itself is used as the config key.
|
|
550
|
+
|
|
551
|
+
Example:
|
|
552
|
+
```yaml
|
|
553
|
+
info:
|
|
554
|
+
title: My API
|
|
555
|
+
x-airbyte-scoping:
|
|
556
|
+
- param: account_id
|
|
557
|
+
config_key: account_id
|
|
558
|
+
- param: workspace_id
|
|
559
|
+
```
|
|
560
|
+
"""
|
|
561
|
+
|
|
537
562
|
|
|
538
563
|
# =============================================================================
|
|
539
564
|
# Enums and Type Definitions
|
|
@@ -622,8 +647,9 @@ def get_all_extension_names() -> list[str]:
|
|
|
622
647
|
AIRBYTE_RECORD_EXTRACTOR,
|
|
623
648
|
AIRBYTE_META_EXTRACTOR,
|
|
624
649
|
AIRBYTE_FILE_URL,
|
|
625
|
-
AIRBYTE_PARAM_SOURCES,
|
|
626
650
|
AIRBYTE_TOKEN_EXTRACT,
|
|
651
|
+
AIRBYTE_ENTITY_RELATIONSHIPS,
|
|
652
|
+
AIRBYTE_SCOPING,
|
|
627
653
|
]
|
|
628
654
|
|
|
629
655
|
|
|
@@ -711,18 +737,24 @@ EXTENSION_REGISTRY = {
|
|
|
711
737
|
"required": "conditional", # Required when action is 'download'
|
|
712
738
|
"description": "Field in metadata response containing download URL (required for download action)",
|
|
713
739
|
},
|
|
714
|
-
AIRBYTE_PARAM_SOURCES: {
|
|
715
|
-
"location": "operation",
|
|
716
|
-
"type": "dict[str, dict[str, str]]",
|
|
717
|
-
"required": False,
|
|
718
|
-
"description": "Per-param source declarations for entity dependency resolution (parent_entity+parent_key or config)",
|
|
719
|
-
},
|
|
720
740
|
AIRBYTE_TOKEN_EXTRACT: {
|
|
721
741
|
"location": "securityScheme",
|
|
722
742
|
"type": "list[str]",
|
|
723
743
|
"required": False,
|
|
724
744
|
"description": "List of fields to extract from OAuth2 token responses and use as server variables",
|
|
725
745
|
},
|
|
746
|
+
AIRBYTE_ENTITY_RELATIONSHIPS: {
|
|
747
|
+
"location": "info",
|
|
748
|
+
"type": "EntityRelationshipConfig",
|
|
749
|
+
"required": False,
|
|
750
|
+
"description": "Foreign-key relationships between entities for dependency resolution and agent introspection",
|
|
751
|
+
},
|
|
752
|
+
AIRBYTE_SCOPING: {
|
|
753
|
+
"location": "info",
|
|
754
|
+
"type": "ScopingParamConfig",
|
|
755
|
+
"required": False,
|
|
756
|
+
"description": "Path parameters resolved from connector config at runtime for tenant-scoped APIs",
|
|
757
|
+
},
|
|
726
758
|
}
|
|
727
759
|
"""
|
|
728
760
|
Complete registry of all Airbyte extensions with metadata.
|
|
@@ -411,7 +411,25 @@ def generate_tool_description(
|
|
|
411
411
|
# Avoid a "PARAMETERS:" header because some docstring parsers treat it as a params section marker.
|
|
412
412
|
lines.append("ENTITIES (ACTIONS + PARAMS):")
|
|
413
413
|
for entity in model.entities:
|
|
414
|
-
|
|
414
|
+
# Emit per-entity AI hints if available
|
|
415
|
+
ai_hints = getattr(entity, "ai_hints", None) or {}
|
|
416
|
+
hint_summary = ai_hints.get("summary") if isinstance(ai_hints, dict) else None
|
|
417
|
+
if hint_summary:
|
|
418
|
+
lines.append(f" {entity.name}: {hint_summary}")
|
|
419
|
+
else:
|
|
420
|
+
lines.append(f" {entity.name}:")
|
|
421
|
+
hint_when = ai_hints.get("when_to_use") if isinstance(ai_hints, dict) else None
|
|
422
|
+
if hint_when:
|
|
423
|
+
lines.append(f" WHEN TO USE: {hint_when}")
|
|
424
|
+
hint_freshness = ai_hints.get("freshness") if isinstance(ai_hints, dict) else None
|
|
425
|
+
if hint_freshness:
|
|
426
|
+
lines.append(f" FRESHNESS: {hint_freshness.upper()}")
|
|
427
|
+
hint_triggers = ai_hints.get("trigger_phrases", []) if isinstance(ai_hints, dict) else []
|
|
428
|
+
if hint_triggers:
|
|
429
|
+
lines.append(f" Trigger phrases: {', '.join(hint_triggers)}")
|
|
430
|
+
hint_search = ai_hints.get("search_strategy") if isinstance(ai_hints, dict) else None
|
|
431
|
+
if hint_search:
|
|
432
|
+
lines.append(f" SEARCH STRATEGY: {hint_search}")
|
|
415
433
|
actions = getattr(entity, "actions", []) or []
|
|
416
434
|
endpoints = getattr(entity, "endpoints", {}) or {}
|
|
417
435
|
for action in actions:
|
|
@@ -425,6 +443,17 @@ def generate_tool_description(
|
|
|
425
443
|
if entity.name in search_field_paths:
|
|
426
444
|
search_sig = _format_search_param_signature()
|
|
427
445
|
lines.append(f" - search{search_sig}")
|
|
446
|
+
# Per-entity example questions from ai_hints
|
|
447
|
+
hint_examples = ai_hints.get("example_questions", []) if isinstance(ai_hints, dict) else []
|
|
448
|
+
if hint_examples:
|
|
449
|
+
lines.append(f" Examples: {'; '.join(hint_examples[:3])}")
|
|
450
|
+
|
|
451
|
+
# Entity relationships
|
|
452
|
+
all_relationships = [r for e in model.entities for r in e.relationships]
|
|
453
|
+
if all_relationships:
|
|
454
|
+
lines.append("ENTITY RELATIONSHIPS:")
|
|
455
|
+
for rel in all_relationships:
|
|
456
|
+
lines.append(f" {rel.format_line()}")
|
|
428
457
|
|
|
429
458
|
# Response structure (brief, includes pagination hint)
|
|
430
459
|
lines.append("RESPONSE STRUCTURE:")
|
|
@@ -458,30 +487,32 @@ def generate_tool_description(
|
|
|
458
487
|
else:
|
|
459
488
|
lines.append(f" {entity_name}: (no fields listed)")
|
|
460
489
|
|
|
461
|
-
# Add example questions
|
|
462
|
-
|
|
463
|
-
if
|
|
464
|
-
|
|
465
|
-
if
|
|
466
|
-
|
|
467
|
-
if
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
490
|
+
# Add example questions — try direct model field first, fall back to openapi_spec
|
|
491
|
+
example_questions = getattr(model, "example_questions", None)
|
|
492
|
+
if not example_questions:
|
|
493
|
+
openapi_spec = getattr(model, "openapi_spec", None)
|
|
494
|
+
if openapi_spec:
|
|
495
|
+
info = getattr(openapi_spec, "info", None)
|
|
496
|
+
if info:
|
|
497
|
+
example_questions = getattr(info, "x_airbyte_example_questions", None)
|
|
498
|
+
if example_questions:
|
|
499
|
+
direct_questions = getattr(example_questions, "direct", None)
|
|
500
|
+
search_questions = getattr(example_questions, "search", None)
|
|
501
|
+
|
|
502
|
+
direct_questions = direct_questions if isinstance(direct_questions, list) else []
|
|
503
|
+
search_questions = search_questions if isinstance(search_questions, list) else []
|
|
504
|
+
|
|
505
|
+
selected_questions: list[str] = []
|
|
506
|
+
if direct_questions or search_questions:
|
|
507
|
+
if enable_hosted_mode_features and search_questions:
|
|
508
|
+
selected_questions = list(search_questions)
|
|
509
|
+
else:
|
|
510
|
+
selected_questions = list(direct_questions) or list(search_questions)
|
|
511
|
+
|
|
512
|
+
if selected_questions:
|
|
513
|
+
lines.append("EXAMPLE QUESTIONS:")
|
|
514
|
+
for q in selected_questions[:MAX_EXAMPLE_QUESTIONS]:
|
|
515
|
+
lines.append(f" - {q}")
|
|
485
516
|
|
|
486
517
|
# Generic parameter description for function signature
|
|
487
518
|
lines.append("FUNCTION PARAMETERS:")
|
|
@@ -26,7 +26,7 @@ from .components import (
|
|
|
26
26
|
Schema,
|
|
27
27
|
)
|
|
28
28
|
from .connector import ExternalDocs, OpenAPIConnector, Tag
|
|
29
|
-
from .extensions import RetryConfig
|
|
29
|
+
from .extensions import EntityRelationshipConfig, RetryConfig, ScopingParamConfig
|
|
30
30
|
from .operations import Operation, PathItem
|
|
31
31
|
from .security import (
|
|
32
32
|
AirbyteAuthConfig,
|
|
@@ -68,4 +68,6 @@ __all__ = [
|
|
|
68
68
|
"Operation",
|
|
69
69
|
# Extension models
|
|
70
70
|
"RetryConfig",
|
|
71
|
+
"EntityRelationshipConfig",
|
|
72
|
+
"ScopingParamConfig",
|
|
71
73
|
]
|
|
@@ -13,7 +13,7 @@ from uuid import UUID
|
|
|
13
13
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
14
14
|
from pydantic_core import Url
|
|
15
15
|
|
|
16
|
-
from .extensions import CacheConfig, ReplicationConfig, RetryConfig
|
|
16
|
+
from .extensions import CacheConfig, EntityRelationshipConfig, ReplicationConfig, RetryConfig, ScopingParamConfig
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class ExampleQuestions(BaseModel):
|
|
@@ -111,6 +111,8 @@ class Info(BaseModel):
|
|
|
111
111
|
- x-airbyte-example-questions: Example questions for AI connector README (Airbyte extension)
|
|
112
112
|
- x-airbyte-cache: Cache configuration for field mapping between API and cache schemas (Airbyte extension)
|
|
113
113
|
- x-airbyte-replication-config: Replication configuration for MULTI mode connectors (Airbyte extension)
|
|
114
|
+
- x-airbyte-entity-relationships: Entity relationship declarations (Airbyte extension)
|
|
115
|
+
- x-airbyte-scoping: Scoping parameter resolution from config (Airbyte extension)
|
|
114
116
|
"""
|
|
115
117
|
|
|
116
118
|
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
@@ -129,6 +131,10 @@ class Info(BaseModel):
|
|
|
129
131
|
x_airbyte_retry_config: RetryConfig | None = Field(None, alias="x-airbyte-retry-config")
|
|
130
132
|
x_airbyte_example_questions: ExampleQuestions | None = Field(None, alias="x-airbyte-example-questions")
|
|
131
133
|
x_airbyte_cache: CacheConfig | None = Field(None, alias="x-airbyte-cache")
|
|
134
|
+
x_airbyte_entity_relationships: list[EntityRelationshipConfig] = Field(
|
|
135
|
+
default_factory=list, alias="x-airbyte-entity-relationships"
|
|
136
|
+
)
|
|
137
|
+
x_airbyte_scoping: list[ScopingParamConfig] = Field(default_factory=list, alias="x-airbyte-scoping")
|
|
132
138
|
x_airbyte_replication_config: ReplicationConfig | None = Field(None, alias="x-airbyte-replication-config")
|
|
133
139
|
x_airbyte_replication_version: str | None = Field(
|
|
134
140
|
default=None,
|
|
@@ -14,6 +14,24 @@ from pydantic import BaseModel, ConfigDict, Field
|
|
|
14
14
|
from .security import SecurityScheme
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
class AiHints(BaseModel):
|
|
18
|
+
"""
|
|
19
|
+
AI hints for entity schemas — helps LLMs understand when and how to use an entity.
|
|
20
|
+
|
|
21
|
+
Attached to entity response schemas via x-airbyte-ai-hints extension.
|
|
22
|
+
Used by generate_tool_description() to emit per-entity guidance.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
26
|
+
|
|
27
|
+
summary: str | None = Field(None, description="One-line description of what this entity contains")
|
|
28
|
+
when_to_use: str | None = Field(None, description="When an LLM should query this entity")
|
|
29
|
+
trigger_phrases: List[str] = Field(default_factory=list, description="Example user queries that should trigger this entity")
|
|
30
|
+
freshness: Literal["live", "static"] | None = Field(None, description="Whether data is live or static")
|
|
31
|
+
example_questions: List[str] = Field(default_factory=list, description="Example questions answerable by this entity")
|
|
32
|
+
search_strategy: str | None = Field(None, description="Hint for LLMs on how to construct effective search queries for this entity")
|
|
33
|
+
|
|
34
|
+
|
|
17
35
|
class Schema(BaseModel):
|
|
18
36
|
"""
|
|
19
37
|
JSON Schema definition for data models.
|
|
@@ -68,6 +86,7 @@ class Schema(BaseModel):
|
|
|
68
86
|
# Airbyte extensions
|
|
69
87
|
x_airbyte_entity_name: str | None = Field(None, alias="x-airbyte-entity-name")
|
|
70
88
|
x_airbyte_stream_name: str | None = Field(None, alias="x-airbyte-stream-name")
|
|
89
|
+
x_airbyte_ai_hints: AiHints | None = Field(None, alias="x-airbyte-ai-hints")
|
|
71
90
|
|
|
72
91
|
|
|
73
92
|
class Parameter(BaseModel):
|
|
@@ -5,6 +5,8 @@ Provides Pydantic models for OpenAPI x-airbyte-* extensions:
|
|
|
5
5
|
- RetryConfig: retry strategy with exponential backoff
|
|
6
6
|
- CacheConfig / CacheEntityConfig / CacheFieldConfig: cache mapping for api_search
|
|
7
7
|
- ReplicationConfig: replication settings for MULTI mode connectors
|
|
8
|
+
- EntityRelationshipConfig: entity relationship declarations
|
|
9
|
+
- ScopingParamConfig: scoping parameter resolution from config
|
|
8
10
|
"""
|
|
9
11
|
|
|
10
12
|
from typing import Literal
|
|
@@ -284,3 +286,73 @@ class CacheConfig(BaseModel):
|
|
|
284
286
|
if entity.entity == user_entity:
|
|
285
287
|
return entity
|
|
286
288
|
return None
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class EntityRelationshipConfig(BaseModel):
|
|
292
|
+
"""
|
|
293
|
+
Entity relationship declaration for cross-entity navigation.
|
|
294
|
+
|
|
295
|
+
Defines a foreign-key relationship between two entities, enabling
|
|
296
|
+
the runtime to resolve parent-child dependencies and provide
|
|
297
|
+
relationship metadata to agents.
|
|
298
|
+
|
|
299
|
+
Used in x-airbyte-entity-relationships extension in the Info object.
|
|
300
|
+
|
|
301
|
+
Example YAML usage:
|
|
302
|
+
info:
|
|
303
|
+
title: My API
|
|
304
|
+
x-airbyte-entity-relationships:
|
|
305
|
+
- source_entity: contacts
|
|
306
|
+
target_entity: accounts
|
|
307
|
+
foreign_key: account_id
|
|
308
|
+
cardinality: many_to_one
|
|
309
|
+
description: "Contact belongs to an account"
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
model_config = ConfigDict(extra="forbid")
|
|
313
|
+
|
|
314
|
+
source_entity: str = Field(description="Entity that holds the foreign key")
|
|
315
|
+
target_entity: str = Field(description="Entity being referenced")
|
|
316
|
+
foreign_key: str = Field(description="Field on source_entity that references target_entity")
|
|
317
|
+
target_key: str = Field(default="id", description="Field on target_entity being referenced")
|
|
318
|
+
cardinality: Literal["one_to_one", "one_to_many", "many_to_one", "many_to_many"] | None = Field(
|
|
319
|
+
None, description="Optional relationship cardinality"
|
|
320
|
+
)
|
|
321
|
+
description: str | None = Field(
|
|
322
|
+
None, description="Human-readable description of the relationship"
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
def format_line(self) -> str:
|
|
326
|
+
"""Format as a human-readable line for tool descriptions."""
|
|
327
|
+
line = f"{self.source_entity} -> {self.target_entity} (via {self.foreign_key}"
|
|
328
|
+
if self.cardinality:
|
|
329
|
+
line += f", {self.cardinality.replace('_', '-')}"
|
|
330
|
+
line += ")"
|
|
331
|
+
if self.description:
|
|
332
|
+
line += f" -- {self.description}"
|
|
333
|
+
return line
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class ScopingParamConfig(BaseModel):
|
|
337
|
+
"""
|
|
338
|
+
Scoping parameter resolution from connector configuration.
|
|
339
|
+
|
|
340
|
+
Declares a path parameter that should be resolved from the connector's
|
|
341
|
+
config values at runtime, rather than being supplied per-request.
|
|
342
|
+
|
|
343
|
+
Used in x-airbyte-scoping extension in the Info object.
|
|
344
|
+
|
|
345
|
+
Example YAML usage:
|
|
346
|
+
info:
|
|
347
|
+
title: My API
|
|
348
|
+
x-airbyte-scoping:
|
|
349
|
+
- param: account_id
|
|
350
|
+
config_key: account_id
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
model_config = ConfigDict(extra="forbid")
|
|
354
|
+
|
|
355
|
+
param: str = Field(description="Path parameter name to resolve from config")
|
|
356
|
+
config_key: str | None = Field(
|
|
357
|
+
None, description="Config key to read. Defaults to param name if omitted."
|
|
358
|
+
)
|
|
@@ -74,14 +74,6 @@ class Operation(BaseModel):
|
|
|
74
74
|
"Example: {'pagination': '$.pagination', 'request_id': '$.requestId'}"
|
|
75
75
|
),
|
|
76
76
|
)
|
|
77
|
-
x_airbyte_param_sources: Dict[str, Dict[str, str]] | None = Field(
|
|
78
|
-
None,
|
|
79
|
-
alias="x-airbyte-param-sources",
|
|
80
|
-
description=(
|
|
81
|
-
"Per-param source declarations for entity dependency resolution. "
|
|
82
|
-
"Keys are param names; values have 'parent_entity'+'parent_key' or 'config' keys."
|
|
83
|
-
),
|
|
84
|
-
)
|
|
85
77
|
x_airbyte_file_url: str | None = Field(None, alias="x-airbyte-file-url")
|
|
86
78
|
x_airbyte_untested: bool | None = Field(
|
|
87
79
|
None,
|
|
@@ -10,7 +10,7 @@ from pydantic import AliasChoices, BaseModel, ConfigDict, Field
|
|
|
10
10
|
|
|
11
11
|
from .constants import OPENAPI_DEFAULT_VERSION
|
|
12
12
|
from .schema.components import PathOverrideConfig
|
|
13
|
-
from .schema.extensions import RetryConfig
|
|
13
|
+
from .schema.extensions import EntityRelationshipConfig, RetryConfig, ScopingParamConfig
|
|
14
14
|
from .schema.security import AirbyteAuthConfig
|
|
15
15
|
|
|
16
16
|
|
|
@@ -304,14 +304,6 @@ class EndpointDefinition(BaseModel):
|
|
|
304
304
|
description="Mark this operation as preferred for health checks (from x-airbyte-preferred-for-check extension)",
|
|
305
305
|
)
|
|
306
306
|
|
|
307
|
-
param_sources: dict[str, dict[str, str]] = Field(
|
|
308
|
-
default_factory=dict,
|
|
309
|
-
description=(
|
|
310
|
-
"Per-param source declarations: "
|
|
311
|
-
"{param_name: {'parent_entity': ..., 'parent_key': ...} | {'config': ...}}."
|
|
312
|
-
),
|
|
313
|
-
)
|
|
314
|
-
|
|
315
307
|
upload_file_param: str | None = Field(
|
|
316
308
|
None,
|
|
317
309
|
description="Parameter name containing base64-encoded file content for multipart/related uploads (from x-airbyte-upload-file-param)",
|
|
@@ -336,6 +328,14 @@ class EntityDefinition(BaseModel):
|
|
|
336
328
|
actions: list[Action]
|
|
337
329
|
endpoints: dict[Action, EndpointDefinition]
|
|
338
330
|
entity_schema: dict[str, Any] | None = Field(default=None, alias="schema")
|
|
331
|
+
ai_hints: dict[str, Any] | None = Field(
|
|
332
|
+
default=None,
|
|
333
|
+
description="AI hints for this entity (from x-airbyte-ai-hints schema extension)",
|
|
334
|
+
)
|
|
335
|
+
relationships: list[EntityRelationshipConfig] = Field(
|
|
336
|
+
default_factory=list,
|
|
337
|
+
description="Relationships where this entity is the source (from x-airbyte-entity-relationships)",
|
|
338
|
+
)
|
|
339
339
|
|
|
340
340
|
|
|
341
341
|
class ConnectorModel(BaseModel):
|
|
@@ -352,6 +352,11 @@ class ConnectorModel(BaseModel):
|
|
|
352
352
|
openapi_spec: Any | None = None # Optional reference to OpenAPIConnector
|
|
353
353
|
retry_config: RetryConfig | None = None # Optional retry configuration
|
|
354
354
|
search_field_paths: dict[str, list[str]] | None = None
|
|
355
|
+
example_questions: Any | None = None # ExampleQuestions from x-airbyte-example-questions
|
|
356
|
+
scoping: list[ScopingParamConfig] = Field(
|
|
357
|
+
default_factory=list,
|
|
358
|
+
description="Scoping parameters resolved from config at runtime (from x-airbyte-scoping)",
|
|
359
|
+
)
|
|
355
360
|
server_variable_defaults: dict[str, str] = Field(
|
|
356
361
|
default_factory=dict,
|
|
357
362
|
description="Default values for server URL variables from the OpenAPI spec. "
|
|
@@ -632,16 +632,33 @@ def validate_meta_extractor_fields(
|
|
|
632
632
|
return True, errors, warnings
|
|
633
633
|
|
|
634
634
|
|
|
635
|
-
def
|
|
636
|
-
"""Check that
|
|
635
|
+
def _check_entity_relationships(config: ConnectorModel) -> List[str]:
|
|
636
|
+
"""Check that entity relationship target_entity values reference real entities.
|
|
637
|
+
|
|
638
|
+
Returns a list of warning strings for relationships where target_entity
|
|
639
|
+
does not match any defined entity name.
|
|
640
|
+
"""
|
|
641
|
+
warnings: List[str] = []
|
|
642
|
+
entity_names = {e.name for e in config.entities}
|
|
643
|
+
for entity in config.entities:
|
|
644
|
+
for rel in entity.relationships:
|
|
645
|
+
if rel.target_entity not in entity_names:
|
|
646
|
+
warnings.append(
|
|
647
|
+
f"Entity '{entity.name}' has relationship with target_entity " f"'{rel.target_entity}' which does not match any defined entity."
|
|
648
|
+
)
|
|
649
|
+
return warnings
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _check_entity_relationships_coverage(config: ConnectorModel) -> List[str]:
|
|
653
|
+
"""Check that endpoints with path params are covered by entity relationships or scoping.
|
|
637
654
|
|
|
638
655
|
Returns a list of warning strings for endpoints where path parameters
|
|
639
|
-
are not covered by
|
|
640
|
-
|
|
656
|
+
are not covered by entity relationships, scoping, or implicit config
|
|
657
|
+
resolution.
|
|
641
658
|
|
|
642
659
|
Skips single-record actions (get, update, delete) when the entity also has
|
|
643
660
|
a list action, since those path params are the entity's own primary key
|
|
644
|
-
resolved from list results
|
|
661
|
+
resolved from list results -- not a parent dependency.
|
|
645
662
|
"""
|
|
646
663
|
warnings: List[str] = []
|
|
647
664
|
|
|
@@ -656,10 +673,15 @@ def _check_param_sources_coverage(config: ConnectorModel) -> List[str]:
|
|
|
656
673
|
implicit_config_keys.update(config.auth.user_config_spec.properties.keys())
|
|
657
674
|
implicit_config_keys.update(config.server_variable_defaults.keys())
|
|
658
675
|
|
|
659
|
-
|
|
676
|
+
# Collect scoping param names
|
|
677
|
+
scoping_params = {s.param for s in config.scoping}
|
|
678
|
+
|
|
679
|
+
single_record_actions = {Action.GET, Action.UPDATE, Action.DELETE, Action.DOWNLOAD}
|
|
660
680
|
|
|
661
681
|
for entity in config.entities:
|
|
662
682
|
has_list = Action.LIST in entity.endpoints
|
|
683
|
+
# Build set of foreign_keys covered by entity relationships
|
|
684
|
+
relationship_keys = {rel.foreign_key for rel in entity.relationships}
|
|
663
685
|
for action, endpoint in entity.endpoints.items():
|
|
664
686
|
if not endpoint.path_params:
|
|
665
687
|
continue
|
|
@@ -668,16 +690,18 @@ def _check_param_sources_coverage(config: ConnectorModel) -> List[str]:
|
|
|
668
690
|
if has_list and action in single_record_actions:
|
|
669
691
|
continue
|
|
670
692
|
for param in endpoint.path_params:
|
|
671
|
-
if param in
|
|
693
|
+
if param in relationship_keys:
|
|
694
|
+
continue
|
|
695
|
+
if param in scoping_params:
|
|
672
696
|
continue
|
|
673
697
|
# SDK resolves implicitly when param name matches a config key
|
|
674
698
|
if param in implicit_config_keys:
|
|
675
699
|
continue
|
|
676
700
|
warnings.append(
|
|
677
701
|
f"Entity '{entity.name}' operation '{action.value}' has path parameter "
|
|
678
|
-
f"'{param}' with no
|
|
679
|
-
f"Add
|
|
680
|
-
f"
|
|
702
|
+
f"'{param}' with no entity relationship or scoping declaration. "
|
|
703
|
+
f"Add an x-airbyte-entity-relationships entry or x-airbyte-scoping entry "
|
|
704
|
+
f"to enable per-entity health checks."
|
|
681
705
|
)
|
|
682
706
|
return warnings
|
|
683
707
|
|
|
@@ -992,8 +1016,7 @@ def validate_connector_readiness(connector_dir: str | Path) -> Dict[str, Any]:
|
|
|
992
1016
|
# All agent connectors must have a replication counterpart in the Airbyte registry
|
|
993
1017
|
if not replication_result.get("registry_found", False):
|
|
994
1018
|
replication_errors.append(
|
|
995
|
-
"No replication connector found in Airbyte registry. "
|
|
996
|
-
"All agent connectors must have a corresponding replication connector."
|
|
1019
|
+
"No replication connector found in Airbyte registry. " "All agent connectors must have a corresponding replication connector."
|
|
997
1020
|
)
|
|
998
1021
|
|
|
999
1022
|
total_errors += len(replication_errors)
|
|
@@ -1054,10 +1077,25 @@ def validate_connector_readiness(connector_dir: str | Path) -> Dict[str, Any]:
|
|
|
1054
1077
|
"to enable reliable health checks."
|
|
1055
1078
|
)
|
|
1056
1079
|
|
|
1057
|
-
# Check for missing
|
|
1058
|
-
|
|
1059
|
-
readiness_warnings.extend(
|
|
1060
|
-
total_warnings += len(
|
|
1080
|
+
# Check for missing entity relationship / scoping declarations
|
|
1081
|
+
relationship_coverage_warnings = _check_entity_relationships_coverage(config)
|
|
1082
|
+
readiness_warnings.extend(relationship_coverage_warnings)
|
|
1083
|
+
total_warnings += len(relationship_coverage_warnings)
|
|
1084
|
+
|
|
1085
|
+
# Check entity relationship target_entity references
|
|
1086
|
+
relationship_warnings = _check_entity_relationships(config)
|
|
1087
|
+
readiness_warnings.extend(relationship_warnings)
|
|
1088
|
+
total_warnings += len(relationship_warnings)
|
|
1089
|
+
|
|
1090
|
+
# Check for missing x-airbyte-ai-hints on entities
|
|
1091
|
+
entities_missing_hints = [entity.name for entity in config.entities if not getattr(entity, "ai_hints", None)]
|
|
1092
|
+
if entities_missing_hints:
|
|
1093
|
+
readiness_warnings.append(
|
|
1094
|
+
f"Entities missing x-airbyte-ai-hints: {', '.join(entities_missing_hints)}. "
|
|
1095
|
+
"Add AI hints (summary, when_to_use, trigger_phrases, freshness) to each entity schema "
|
|
1096
|
+
"to improve LLM tool selection. See get_connector_yaml_schema_docs('extensions') for details."
|
|
1097
|
+
)
|
|
1098
|
+
total_warnings += 1
|
|
1061
1099
|
|
|
1062
1100
|
if total_cassettes > 0:
|
|
1063
1101
|
readiness_warnings.append(
|
{airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/connector.py
RENAMED
|
@@ -108,7 +108,7 @@ from .models import (
|
|
|
108
108
|
# TypeVar for decorator type preservation
|
|
109
109
|
_F = TypeVar("_F", bound=Callable[..., Any])
|
|
110
110
|
|
|
111
|
-
DEFAULT_MAX_OUTPUT_CHARS =
|
|
111
|
+
DEFAULT_MAX_OUTPUT_CHARS = 100_000 # ~100KB default, configurable per-tool
|
|
112
112
|
|
|
113
113
|
|
|
114
114
|
def _raise_output_too_large(message: str) -> None:
|
|
@@ -20,6 +20,9 @@ from ._vendored.connector_sdk.schema.security import (
|
|
|
20
20
|
AirbyteAuthConfig,
|
|
21
21
|
AuthConfigFieldSpec,
|
|
22
22
|
)
|
|
23
|
+
from ._vendored.connector_sdk.schema.base import (
|
|
24
|
+
ExampleQuestions,
|
|
25
|
+
)
|
|
23
26
|
from ._vendored.connector_sdk.schema.components import (
|
|
24
27
|
PathOverrideConfig,
|
|
25
28
|
)
|
|
@@ -2899,4 +2902,27 @@ GithubConnectorModel: ConnectorModel = ConnectorModel(
|
|
|
2899
2902
|
},
|
|
2900
2903
|
),
|
|
2901
2904
|
],
|
|
2905
|
+
example_questions=ExampleQuestions(
|
|
2906
|
+
direct=[
|
|
2907
|
+
'Show me all open issues in my repositories this month',
|
|
2908
|
+
"List the top 5 repositories I've starred recently",
|
|
2909
|
+
'Analyze the commit trends in my main project over the last quarter',
|
|
2910
|
+
'Find all pull requests created in the past two weeks',
|
|
2911
|
+
'Search for repositories related to machine learning in my organizations',
|
|
2912
|
+
'Compare the number of contributors across my different team projects',
|
|
2913
|
+
'Identify the most active branches in my main repository',
|
|
2914
|
+
'Get details about the most recent releases in my organization',
|
|
2915
|
+
'List all milestones for our current development sprint',
|
|
2916
|
+
'Show me insights about pull request review patterns in our team',
|
|
2917
|
+
'List all unanswered discussions in a repository',
|
|
2918
|
+
'Show me recent discussions in the General category',
|
|
2919
|
+
],
|
|
2920
|
+
unsupported=[
|
|
2921
|
+
'Create a new issue in the project repository',
|
|
2922
|
+
'Update the status of this pull request',
|
|
2923
|
+
'Delete an old branch from the repository',
|
|
2924
|
+
'Schedule a team review for this code',
|
|
2925
|
+
'Assign a new label to this issue',
|
|
2926
|
+
],
|
|
2927
|
+
),
|
|
2902
2928
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/__init__.py
RENAMED
|
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
|
{airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/models.py
RENAMED
|
File without changes
|
{airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/types.py
RENAMED
|
File without changes
|