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.
Files changed (67) hide show
  1. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/CHANGELOG.md +5 -0
  2. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/PKG-INFO +3 -3
  3. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/README.md +2 -2
  4. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/connector_model_loader.py +35 -6
  5. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/executor/local_executor.py +59 -19
  6. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/extensions.py +74 -42
  7. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/introspection.py +56 -25
  8. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/schema/__init__.py +3 -1
  9. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/schema/base.py +7 -1
  10. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/schema/components.py +19 -0
  11. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/schema/extensions.py +72 -0
  12. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/schema/operations.py +0 -8
  13. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/types.py +14 -9
  14. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/validation/readiness.py +54 -16
  15. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/connector.py +1 -1
  16. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/connector_model.py +26 -0
  17. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/pyproject.toml +1 -1
  18. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/.gitignore +0 -0
  19. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/AUTH.md +0 -0
  20. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/REFERENCE.md +0 -0
  21. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/__init__.py +0 -0
  22. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/__init__.py +0 -0
  23. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/__init__.py +0 -0
  24. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/auth_strategies.py +0 -0
  25. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/auth_template.py +0 -0
  26. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/cloud_utils/__init__.py +0 -0
  27. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/cloud_utils/client.py +0 -0
  28. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/constants.py +0 -0
  29. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/exceptions.py +0 -0
  30. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/executor/__init__.py +0 -0
  31. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/executor/hosted_executor.py +0 -0
  32. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/executor/models.py +0 -0
  33. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/http/__init__.py +0 -0
  34. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/http/adapters/__init__.py +0 -0
  35. {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
  36. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/http/config.py +0 -0
  37. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/http/exceptions.py +0 -0
  38. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/http/protocols.py +0 -0
  39. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/http/response.py +0 -0
  40. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/http_client.py +0 -0
  41. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/logging/__init__.py +0 -0
  42. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/logging/logger.py +0 -0
  43. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/logging/types.py +0 -0
  44. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/observability/__init__.py +0 -0
  45. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/observability/config.py +0 -0
  46. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/observability/models.py +0 -0
  47. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/observability/redactor.py +0 -0
  48. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/observability/session.py +0 -0
  49. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/performance/__init__.py +0 -0
  50. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/performance/instrumentation.py +0 -0
  51. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/performance/metrics.py +0 -0
  52. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/schema/connector.py +0 -0
  53. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/schema/security.py +0 -0
  54. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/secrets.py +0 -0
  55. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/telemetry/__init__.py +0 -0
  56. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/telemetry/config.py +0 -0
  57. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/telemetry/events.py +0 -0
  58. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/telemetry/tracker.py +0 -0
  59. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/utils.py +0 -0
  60. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/validation/__init__.py +0 -0
  61. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/validation/cache.py +0 -0
  62. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/validation/manifest.py +0 -0
  63. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/validation/models.py +0 -0
  64. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/validation/overview.py +0 -0
  65. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/_vendored/connector_sdk/validation/replication.py +0 -0
  66. {airbyte_agent_github-0.18.128 → airbyte_agent_github-0.18.129}/airbyte_agent_github/models.py +0 -0
  67. {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.128
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.128
170
+ - **Package version:** 0.18.129
171
171
  - **Connector version:** 0.1.17
172
- - **Generated with Connector SDK commit SHA:** e4c7493336bcabf5bea5a761ab7b4edfe2a606a5
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.128
137
+ - **Package version:** 0.18.129
138
138
  - **Connector version:** 0.1.17
139
- - **Generated with Connector SDK commit SHA:** e4c7493336bcabf5bea5a761ab7b4edfe2a606a5
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 stream_name from components if available
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 param_sources.
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 plus any
704
- # query/body params that have explicit param_sources annotations
705
- # (covers GraphQL connectors where all params are query params).
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
- if endpoint.param_sources:
708
- for param_name in endpoint.param_sources:
709
- if param_name not in params_needing_resolution:
710
- params_needing_resolution.append(param_name)
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._resolve_param_sources(
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 _resolve_param_sources(
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 x-airbyte-param-sources annotations and config_values.
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
- source = endpoint.param_sources.get(param_name, {})
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
- config_key = source.get("config") or param_name
770
- if config_key in self.config_values:
771
- resolved[param_name] = self.config_values[config_key]
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
- parent_entity_name = source.get("parent_entity")
775
- parent_key = source.get("parent_key")
776
- if not parent_entity_name or not parent_key:
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._resolve_param_sources(
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
- lines.append(f" {entity.name}:")
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 if available in openapi_spec
462
- openapi_spec = getattr(model, "openapi_spec", None)
463
- if openapi_spec:
464
- info = getattr(openapi_spec, "info", None)
465
- if info:
466
- example_questions = getattr(info, "x_airbyte_example_questions", None)
467
- if example_questions:
468
- direct_questions = getattr(example_questions, "direct", None)
469
- search_questions = getattr(example_questions, "search", None)
470
-
471
- direct_questions = direct_questions if isinstance(direct_questions, list) else []
472
- search_questions = search_questions if isinstance(search_questions, list) else []
473
-
474
- selected_questions: list[str] = []
475
- if direct_questions or search_questions:
476
- if enable_hosted_mode_features and search_questions:
477
- selected_questions = list(search_questions)
478
- else:
479
- selected_questions = list(direct_questions) or list(search_questions)
480
-
481
- if selected_questions:
482
- lines.append("EXAMPLE QUESTIONS:")
483
- for q in selected_questions[:MAX_EXAMPLE_QUESTIONS]:
484
- lines.append(f" - {q}")
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 _check_param_sources_coverage(config: ConnectorModel) -> List[str]:
636
- """Check that endpoints with path params have x-airbyte-param-sources declarations.
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 param_sources and are not implicitly resolvable from
640
- config (auth properties, server variables).
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 not a parent dependency.
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
- single_record_actions = {Action.GET, Action.UPDATE, Action.DELETE}
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 endpoint.param_sources:
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 x-airbyte-param-sources declaration. "
679
- f"Add param-sources to enable per-entity health checks. "
680
- f"See get_connector_yaml_schema_docs('extensions') for guidance."
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 x-airbyte-param-sources declarations
1058
- param_source_warnings = _check_param_sources_coverage(config)
1059
- readiness_warnings.extend(param_source_warnings)
1060
- total_warnings += len(param_source_warnings)
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(
@@ -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 = 50_000 # ~50KB default, configurable per-tool
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
  )
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "airbyte-agent-github"
3
- version = "0.18.128"
3
+ version = "0.18.129"
4
4
  description = "Airbyte Github Connector for AI platforms"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"