airbyte-agent-slack 0.1.21__py3-none-any.whl → 0.1.22__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -145,6 +145,87 @@ def _deproxy_schema(obj: Any) -> Any:
145
145
  return obj
146
146
 
147
147
 
148
+ def _type_includes(type_value: Any, target: str) -> bool:
149
+ if isinstance(type_value, list):
150
+ return target in type_value
151
+ return type_value == target
152
+
153
+
154
+ def _flatten_cache_properties(properties: dict[str, Any], prefix: str) -> list[str]:
155
+ entries: list[str] = []
156
+ for prop_name, prop in properties.items():
157
+ path = f"{prefix}{prop_name}" if prefix else prop_name
158
+ entries.append(path)
159
+
160
+ prop_type = getattr(prop, "type", None) if not isinstance(prop, dict) else prop.get("type")
161
+ prop_properties = getattr(prop, "properties", None) if not isinstance(prop, dict) else prop.get("properties")
162
+
163
+ if _type_includes(prop_type, "array"):
164
+ array_path = f"{path}[]"
165
+ entries.append(array_path)
166
+ if isinstance(prop_properties, dict):
167
+ entries.extend(_flatten_cache_properties(prop_properties, prefix=f"{array_path}."))
168
+ elif isinstance(prop_properties, dict):
169
+ entries.extend(_flatten_cache_properties(prop_properties, prefix=f"{path}."))
170
+
171
+ return entries
172
+
173
+
174
+ def _flatten_cache_field_paths(field: Any) -> list[str]:
175
+ field_name = getattr(field, "name", None) if not isinstance(field, dict) else field.get("name")
176
+ if not isinstance(field_name, str) or not field_name:
177
+ return []
178
+
179
+ field_type = getattr(field, "type", None) if not isinstance(field, dict) else field.get("type")
180
+ field_properties = getattr(field, "properties", None) if not isinstance(field, dict) else field.get("properties")
181
+
182
+ entries = [field_name]
183
+ if _type_includes(field_type, "array"):
184
+ array_path = f"{field_name}[]"
185
+ entries.append(array_path)
186
+ if isinstance(field_properties, dict):
187
+ entries.extend(_flatten_cache_properties(field_properties, prefix=f"{array_path}."))
188
+ elif isinstance(field_properties, dict):
189
+ entries.extend(_flatten_cache_properties(field_properties, prefix=f"{field_name}."))
190
+
191
+ return entries
192
+
193
+
194
+ def _dedupe_strings(values: list[str]) -> list[str]:
195
+ seen: set[str] = set()
196
+ ordered: list[str] = []
197
+ for value in values:
198
+ if value not in seen:
199
+ seen.add(value)
200
+ ordered.append(value)
201
+ return ordered
202
+
203
+
204
+ def _extract_search_field_paths(spec: OpenAPIConnector) -> dict[str, list[str]]:
205
+ cache_config = getattr(spec.info, "x_airbyte_cache", None)
206
+ entities = getattr(cache_config, "entities", None)
207
+ if not isinstance(entities, list):
208
+ return {}
209
+
210
+ search_fields: dict[str, list[str]] = {}
211
+ for entity in entities:
212
+ entity_name = getattr(entity, "entity", None) if not isinstance(entity, dict) else entity.get("entity")
213
+ if not isinstance(entity_name, str) or not entity_name:
214
+ continue
215
+
216
+ fields = getattr(entity, "fields", None) if not isinstance(entity, dict) else entity.get("fields")
217
+ if not isinstance(fields, list):
218
+ continue
219
+
220
+ field_paths: list[str] = []
221
+ for field in fields:
222
+ field_paths.extend(_flatten_cache_field_paths(field))
223
+
224
+ search_fields[entity_name] = _dedupe_strings(field_paths)
225
+
226
+ return search_fields
227
+
228
+
148
229
  def parse_openapi_spec(raw_config: dict) -> OpenAPIConnector:
149
230
  """Parse OpenAPI specification from YAML.
150
231
 
@@ -434,6 +515,8 @@ def convert_openapi_to_connector_model(spec: OpenAPIConnector) -> ConnectorModel
434
515
  if not connector_id:
435
516
  raise InvalidOpenAPIError("Missing required x-airbyte-connector-id field")
436
517
 
518
+ search_field_paths = _extract_search_field_paths(spec)
519
+
437
520
  # Create ConnectorModel
438
521
  model = ConnectorModel(
439
522
  id=connector_id,
@@ -444,6 +527,7 @@ def convert_openapi_to_connector_model(spec: OpenAPIConnector) -> ConnectorModel
444
527
  entities=entities,
445
528
  openapi_spec=spec,
446
529
  retry_config=retry_config,
530
+ search_field_paths=search_field_paths,
447
531
  )
448
532
 
449
533
  return model
@@ -18,6 +18,185 @@ from typing import Any, Protocol
18
18
  MAX_EXAMPLE_QUESTIONS = 5 # Maximum number of example questions to include in description
19
19
 
20
20
 
21
+ def _type_includes(type_value: Any, target: str) -> bool:
22
+ if isinstance(type_value, list):
23
+ return target in type_value
24
+ return type_value == target
25
+
26
+
27
+ def _is_object_schema(schema: dict[str, Any]) -> bool:
28
+ if "properties" in schema:
29
+ return True
30
+ return _type_includes(schema.get("type"), "object")
31
+
32
+
33
+ def _is_array_schema(schema: dict[str, Any]) -> bool:
34
+ if "items" in schema:
35
+ return True
36
+ return _type_includes(schema.get("type"), "array")
37
+
38
+
39
+ def _dedupe_param_entries(entries: list[tuple[str, bool]]) -> list[tuple[str, bool]]:
40
+ seen: dict[str, bool] = {}
41
+ ordered: list[str] = []
42
+ for name, required in entries:
43
+ if name not in seen:
44
+ seen[name] = required
45
+ ordered.append(name)
46
+ else:
47
+ seen[name] = seen[name] or required
48
+ return [(name, seen[name]) for name in ordered]
49
+
50
+
51
+ def _flatten_schema_params(
52
+ schema: dict[str, Any],
53
+ prefix: str = "",
54
+ parent_required: bool = True,
55
+ seen_stack: set[int] | None = None,
56
+ ) -> list[tuple[str, bool]]:
57
+ if not isinstance(schema, dict):
58
+ return []
59
+
60
+ if seen_stack is None:
61
+ seen_stack = set()
62
+
63
+ schema_id = id(schema)
64
+ if schema_id in seen_stack:
65
+ return []
66
+
67
+ seen_stack.add(schema_id)
68
+ try:
69
+ entries: list[tuple[str, bool]] = []
70
+
71
+ for subschema in schema.get("allOf", []) or []:
72
+ if isinstance(subschema, dict):
73
+ entries.extend(_flatten_schema_params(subschema, prefix, parent_required, seen_stack))
74
+
75
+ for keyword in ("anyOf", "oneOf"):
76
+ for subschema in schema.get(keyword, []) or []:
77
+ if isinstance(subschema, dict):
78
+ entries.extend(_flatten_schema_params(subschema, prefix, False, seen_stack))
79
+
80
+ properties = schema.get("properties")
81
+ if isinstance(properties, dict):
82
+ required_fields = set(schema.get("required", [])) if isinstance(schema.get("required"), list) else set()
83
+ for prop_name, prop_schema in properties.items():
84
+ path = f"{prefix}{prop_name}" if prefix else prop_name
85
+ is_required = parent_required and prop_name in required_fields
86
+ entries.append((path, is_required))
87
+
88
+ if isinstance(prop_schema, dict):
89
+ if _is_array_schema(prop_schema):
90
+ array_path = f"{path}[]"
91
+ entries.append((array_path, is_required))
92
+ items = prop_schema.get("items")
93
+ if isinstance(items, dict):
94
+ entries.extend(_flatten_schema_params(items, prefix=f"{array_path}.", parent_required=is_required, seen_stack=seen_stack))
95
+ if _is_object_schema(prop_schema):
96
+ entries.extend(_flatten_schema_params(prop_schema, prefix=f"{path}.", parent_required=is_required, seen_stack=seen_stack))
97
+
98
+ return _dedupe_param_entries(entries)
99
+ finally:
100
+ seen_stack.remove(schema_id)
101
+
102
+
103
+ def _cache_field_value(field: Any, key: str) -> Any:
104
+ if isinstance(field, dict):
105
+ return field.get(key)
106
+ return getattr(field, key, None)
107
+
108
+
109
+ def _flatten_cache_properties(properties: dict[str, Any], prefix: str) -> list[str]:
110
+ entries: list[str] = []
111
+ for prop_name, prop in properties.items():
112
+ path = f"{prefix}{prop_name}" if prefix else prop_name
113
+ entries.append(path)
114
+
115
+ prop_type = _cache_field_value(prop, "type")
116
+ prop_properties = _cache_field_value(prop, "properties")
117
+
118
+ if _type_includes(prop_type, "array"):
119
+ array_path = f"{path}[]"
120
+ entries.append(array_path)
121
+ if isinstance(prop_properties, dict):
122
+ entries.extend(_flatten_cache_properties(prop_properties, prefix=f"{array_path}."))
123
+ elif isinstance(prop_properties, dict):
124
+ entries.extend(_flatten_cache_properties(prop_properties, prefix=f"{path}."))
125
+
126
+ return entries
127
+
128
+
129
+ def _flatten_cache_field_paths(field: Any) -> list[str]:
130
+ field_name = _cache_field_value(field, "name")
131
+ if not isinstance(field_name, str) or not field_name:
132
+ return []
133
+
134
+ field_type = _cache_field_value(field, "type")
135
+ field_properties = _cache_field_value(field, "properties")
136
+
137
+ entries = [field_name]
138
+ if _type_includes(field_type, "array"):
139
+ array_path = f"{field_name}[]"
140
+ entries.append(array_path)
141
+ if isinstance(field_properties, dict):
142
+ entries.extend(_flatten_cache_properties(field_properties, prefix=f"{array_path}."))
143
+ elif isinstance(field_properties, dict):
144
+ entries.extend(_flatten_cache_properties(field_properties, prefix=f"{field_name}."))
145
+
146
+ return entries
147
+
148
+
149
+ def _dedupe_strings(values: list[str]) -> list[str]:
150
+ seen: set[str] = set()
151
+ ordered: list[str] = []
152
+ for value in values:
153
+ if value not in seen:
154
+ seen.add(value)
155
+ ordered.append(value)
156
+ return ordered
157
+
158
+
159
+ def _collect_search_field_paths(model: ConnectorModelProtocol) -> dict[str, list[str]]:
160
+ search_field_paths = getattr(model, "search_field_paths", None)
161
+ if isinstance(search_field_paths, dict) and search_field_paths:
162
+ normalized: dict[str, list[str]] = {}
163
+ for entity, fields in search_field_paths.items():
164
+ if not isinstance(entity, str) or not entity:
165
+ continue
166
+ if isinstance(fields, list):
167
+ normalized[entity] = _dedupe_strings([field for field in fields if isinstance(field, str) and field])
168
+ return normalized
169
+
170
+ openapi_spec = getattr(model, "openapi_spec", None)
171
+ info = getattr(openapi_spec, "info", None)
172
+ cache_config = getattr(info, "x_airbyte_cache", None)
173
+ entities = getattr(cache_config, "entities", None)
174
+ if not isinstance(entities, list):
175
+ return {}
176
+
177
+ search_fields: dict[str, list[str]] = {}
178
+ for entity in entities:
179
+ entity_name = _cache_field_value(entity, "entity")
180
+ if not isinstance(entity_name, str) or not entity_name:
181
+ continue
182
+
183
+ fields = _cache_field_value(entity, "fields") or []
184
+ if not isinstance(fields, list):
185
+ continue
186
+ field_paths: list[str] = []
187
+ for field in fields:
188
+ field_paths.extend(_flatten_cache_field_paths(field))
189
+
190
+ search_fields[entity_name] = _dedupe_strings(field_paths)
191
+
192
+ return search_fields
193
+
194
+
195
+ def _format_search_param_signature() -> str:
196
+ params = ["query*", "limit?", "cursor?", "fields?"]
197
+ return f"({', '.join(params)})"
198
+
199
+
21
200
  class EndpointProtocol(Protocol):
22
201
  """Protocol defining the expected interface for endpoint parameters.
23
202
 
@@ -54,6 +233,9 @@ class ConnectorModelProtocol(Protocol):
54
233
  @property
55
234
  def openapi_spec(self) -> Any: ...
56
235
 
236
+ @property
237
+ def search_field_paths(self) -> dict[str, list[str]] | None: ...
238
+
57
239
 
58
240
  def format_param_signature(endpoint: EndpointProtocol) -> str:
59
241
  """Format parameter signature for an endpoint action.
@@ -86,9 +268,12 @@ def format_param_signature(endpoint: EndpointProtocol) -> str:
86
268
  required = schema.get("required", False)
87
269
  params.append(f"{name}{'*' if required else '?'}")
88
270
 
89
- # Body fields
90
- if request_schema:
91
- required_fields = set(request_schema.get("required", []))
271
+ # Body fields (include nested params from schema when available)
272
+ if isinstance(request_schema, dict):
273
+ for name, required in _flatten_schema_params(request_schema):
274
+ params.append(f"{name}{'*' if required else '?'}")
275
+ elif request_schema:
276
+ required_fields = set(request_schema.get("required", [])) if isinstance(request_schema, dict) else set()
92
277
  for name in body_fields:
93
278
  params.append(f"{name}{'*' if name in required_fields else '?'}")
94
279
 
@@ -99,7 +284,7 @@ def describe_entities(model: ConnectorModelProtocol) -> list[dict[str, Any]]:
99
284
  """Generate entity descriptions from ConnectorModel.
100
285
 
101
286
  Returns a list of entity descriptions with detailed parameter information
102
- for each action. This is used by generated connectors' describe() method.
287
+ for each action. This is used by generated connectors' list_entities() method.
103
288
 
104
289
  Args:
105
290
  model: Object conforming to ConnectorModelProtocol (e.g., ConnectorModel)
@@ -203,8 +388,8 @@ def generate_tool_description(model: ConnectorModelProtocol) -> str:
203
388
  - Response structure documentation with pagination hints
204
389
  - Example questions if available in the OpenAPI spec
205
390
 
206
- This is used by the Connector.describe class method decorator to populate
207
- function docstrings for AI framework integration.
391
+ This is used by the Connector.tool_utils decorator to populate function
392
+ docstrings for AI framework integration.
208
393
 
209
394
  Args:
210
395
  model: Object conforming to ConnectorModelProtocol (e.g., ConnectorModel)
@@ -213,8 +398,11 @@ def generate_tool_description(model: ConnectorModelProtocol) -> str:
213
398
  Formatted description string suitable for AI tool documentation
214
399
  """
215
400
  lines = []
401
+ # NOTE: Do not insert blank lines in the docstring; pydantic-ai parsing truncates
402
+ # at the first empty line and only keeps the initial section.
216
403
 
217
404
  # Entity/action parameter details (including pagination params like limit, starting_after)
405
+ search_field_paths = _collect_search_field_paths(model)
218
406
  lines.append("ENTITIES AND PARAMETERS:")
219
407
  for entity in model.entities:
220
408
  lines.append(f" {entity.name}:")
@@ -228,14 +416,41 @@ def generate_tool_description(model: ConnectorModelProtocol) -> str:
228
416
  lines.append(f" - {action_str}{param_sig}")
229
417
  else:
230
418
  lines.append(f" - {action_str}()")
419
+ if entity.name in search_field_paths:
420
+ search_sig = _format_search_param_signature()
421
+ lines.append(f" - search{search_sig}")
231
422
 
232
423
  # Response structure (brief, includes pagination hint)
233
- lines.append("")
234
424
  lines.append("RESPONSE STRUCTURE:")
235
425
  lines.append(" - list/api_search: {data: [...], meta: {has_more: bool}}")
236
426
  lines.append(" - get: Returns entity directly (no envelope)")
237
427
  lines.append(" To paginate: pass starting_after=<last_id> while has_more is true")
238
428
 
429
+ lines.append("GUIDELINES:")
430
+ lines.append(' - Prefer cached search over direct API calls when using execute(): action="search" whenever possible.')
431
+ lines.append(" - Direct API actions (list/get/download) are slower and should be used only if search cannot answer the query.")
432
+ lines.append(" - Keep results small: use params.fields, params.query.filter, small params.limit, and cursor pagination.")
433
+ lines.append(" - If output is too large, refine the query with tighter filters/fields/limit.")
434
+
435
+ if search_field_paths:
436
+ lines.append("SEARCH (PREFERRED):")
437
+ lines.append(' execute(entity, action="search", params={')
438
+ lines.append(' "query": {"filter": <condition>, "sort": [{"field": "asc|desc"}, ...]},')
439
+ lines.append(' "limit": <int>, "cursor": <str>, "fields": ["field", "nested.field", ...]')
440
+ lines.append(" })")
441
+ lines.append(' Example: {"query": {"filter": {"eq": {"title": "Intro to Airbyte | Miinto"}}}, "limit": 1,')
442
+ lines.append(' "fields": ["id", "title", "started", "primaryUserId"]}')
443
+ lines.append(" Conditions are composable:")
444
+ lines.append(" - eq, neq, gt, gte, lt, lte, in, like, fuzzy, keyword, contains, any")
445
+ lines.append(' - and/or/not to combine conditions (e.g., {"and": [cond1, cond2]})')
446
+
447
+ lines.append("SEARCHABLE FIELDS:")
448
+ for entity_name, field_paths in search_field_paths.items():
449
+ if field_paths:
450
+ lines.append(f" {entity_name}: {', '.join(field_paths)}")
451
+ else:
452
+ lines.append(f" {entity_name}: (no fields listed)")
453
+
239
454
  # Add example questions if available in openapi_spec
240
455
  openapi_spec = getattr(model, "openapi_spec", None)
241
456
  if openapi_spec:
@@ -245,18 +460,15 @@ def generate_tool_description(model: ConnectorModelProtocol) -> str:
245
460
  if example_questions:
246
461
  supported = getattr(example_questions, "supported", None)
247
462
  if supported:
248
- lines.append("")
249
463
  lines.append("EXAMPLE QUESTIONS:")
250
464
  for q in supported[:MAX_EXAMPLE_QUESTIONS]:
251
465
  lines.append(f" - {q}")
252
466
 
253
467
  # Generic parameter description for function signature
254
- lines.append("")
255
468
  lines.append("FUNCTION PARAMETERS:")
256
469
  lines.append(" - entity: Entity name (string)")
257
470
  lines.append(" - action: Operation to perform (string)")
258
471
  lines.append(" - params: Operation parameters (dict) - see entity details above")
259
- lines.append("")
260
472
  lines.append("Parameter markers: * = required, ? = optional")
261
473
 
262
474
  return "\n".join(lines)
@@ -252,3 +252,4 @@ class ConnectorModel(BaseModel):
252
252
  entities: list[EntityDefinition]
253
253
  openapi_spec: Any | None = None # Optional reference to OpenAPIConnector
254
254
  retry_config: RetryConfig | None = None # Optional retry configuration
255
+ search_field_paths: dict[str, list[str]] | None = None
@@ -4,8 +4,11 @@ Slack connector.
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
+ import inspect
8
+ import json
7
9
  import logging
8
- from typing import TYPE_CHECKING, Any, Callable, TypeVar, overload
10
+ from functools import wraps
11
+ from typing import TYPE_CHECKING, Any, Callable, Mapping, TypeVar, overload
9
12
  try:
10
13
  from typing import Literal
11
14
  except ImportError:
@@ -62,6 +65,38 @@ from .models import (
62
65
  # TypeVar for decorator type preservation
63
66
  _F = TypeVar("_F", bound=Callable[..., Any])
64
67
 
68
+ DEFAULT_MAX_OUTPUT_CHARS = 50_000 # ~50KB default, configurable per-tool
69
+
70
+
71
+ def _raise_output_too_large(message: str) -> None:
72
+ try:
73
+ from pydantic_ai import ModelRetry # type: ignore[import-not-found]
74
+ except Exception as exc:
75
+ raise RuntimeError(message) from exc
76
+ raise ModelRetry(message)
77
+
78
+
79
+ def _check_output_size(result: Any, max_chars: int | None, tool_name: str) -> Any:
80
+ if max_chars is None or max_chars <= 0:
81
+ return result
82
+
83
+ try:
84
+ serialized = json.dumps(result, default=str)
85
+ except (TypeError, ValueError):
86
+ return result
87
+
88
+ if len(serialized) > max_chars:
89
+ truncated_preview = serialized[:500] + "..." if len(serialized) > 500 else serialized
90
+ _raise_output_too_large(
91
+ f"Tool '{tool_name}' output too large ({len(serialized):,} chars, limit {max_chars:,}). "
92
+ "Please narrow your query by: using the 'fields' parameter to select only needed fields, "
93
+ "adding filters, or reducing the 'limit'. "
94
+ f"Preview: {truncated_preview}"
95
+ )
96
+
97
+ return result
98
+
99
+
65
100
 
66
101
 
67
102
  class SlackConnector:
@@ -314,15 +349,15 @@ class SlackConnector:
314
349
  async def execute(
315
350
  self,
316
351
  entity: str,
317
- action: str,
318
- params: dict[str, Any]
352
+ action: Literal["list", "get", "create", "update", "search"],
353
+ params: Mapping[str, Any]
319
354
  ) -> SlackExecuteResult[Any] | SlackExecuteResultWithMeta[Any, Any] | Any: ...
320
355
 
321
356
  async def execute(
322
357
  self,
323
358
  entity: str,
324
- action: str,
325
- params: dict[str, Any] | None = None
359
+ action: Literal["list", "get", "create", "update", "search"],
360
+ params: Mapping[str, Any] | None = None
326
361
  ) -> Any:
327
362
  """
328
363
  Execute an entity operation with full type safety.
@@ -350,16 +385,17 @@ class SlackConnector:
350
385
  from ._vendored.connector_sdk.executor import ExecutionConfig
351
386
 
352
387
  # Remap parameter names from snake_case (TypedDict keys) to API parameter names
353
- if params:
388
+ resolved_params = dict(params) if params is not None else None
389
+ if resolved_params:
354
390
  param_map = self._PARAM_MAP.get((entity, action), {})
355
391
  if param_map:
356
- params = {param_map.get(k, k): v for k, v in params.items()}
392
+ resolved_params = {param_map.get(k, k): v for k, v in resolved_params.items()}
357
393
 
358
394
  # Use ExecutionConfig for both local and hosted executors
359
395
  config = ExecutionConfig(
360
396
  entity=entity,
361
397
  action=action,
362
- params=params
398
+ params=resolved_params
363
399
  )
364
400
 
365
401
  result = await self._executor.execute(config)
@@ -386,41 +422,67 @@ class SlackConnector:
386
422
  # ===== INTROSPECTION METHODS =====
387
423
 
388
424
  @classmethod
389
- def describe(cls, func: _F) -> _F:
425
+ def tool_utils(
426
+ cls,
427
+ func: _F | None = None,
428
+ *,
429
+ update_docstring: bool = True,
430
+ max_output_chars: int | None = DEFAULT_MAX_OUTPUT_CHARS,
431
+ ) -> _F | Callable[[_F], _F]:
390
432
  """
391
- Decorator that populates a function's docstring with connector capabilities.
392
-
393
- This class method can be used as a decorator to automatically generate
394
- comprehensive documentation for AI tool functions.
433
+ Decorator that adds tool utilities like docstring augmentation and output limits.
395
434
 
396
435
  Usage:
397
436
  @mcp.tool()
398
- @SlackConnector.describe
437
+ @SlackConnector.tool_utils
399
438
  async def execute(entity: str, action: str, params: dict):
400
- '''Execute operations.'''
401
439
  ...
402
440
 
403
- The decorated function's __doc__ will be updated with:
404
- - Available entities and their actions
405
- - Parameter signatures with required (*) and optional (?) markers
406
- - Response structure documentation
407
- - Example questions (if available in OpenAPI spec)
441
+ @mcp.tool()
442
+ @SlackConnector.tool_utils(update_docstring=False, max_output_chars=None)
443
+ async def execute(entity: str, action: str, params: dict):
444
+ ...
408
445
 
409
446
  Args:
410
- func: The function to decorate
411
-
412
- Returns:
413
- The same function with updated __doc__
447
+ update_docstring: When True, append connector capabilities to __doc__.
448
+ max_output_chars: Max serialized output size before raising. Use None to disable.
414
449
  """
415
- description = generate_tool_description(SlackConnectorModel)
416
450
 
417
- original_doc = func.__doc__ or ""
418
- if original_doc.strip():
419
- func.__doc__ = f"{original_doc.strip()}\n{description}"
420
- else:
421
- func.__doc__ = description
451
+ def decorate(inner: _F) -> _F:
452
+ if update_docstring:
453
+ description = generate_tool_description(SlackConnectorModel)
454
+ original_doc = inner.__doc__ or ""
455
+ if original_doc.strip():
456
+ full_doc = f"{original_doc.strip()}\n{description}"
457
+ else:
458
+ full_doc = description
459
+ else:
460
+ full_doc = ""
461
+
462
+ if inspect.iscoroutinefunction(inner):
463
+
464
+ @wraps(inner)
465
+ async def aw(*args: Any, **kwargs: Any) -> Any:
466
+ result = await inner(*args, **kwargs)
467
+ return _check_output_size(result, max_output_chars, inner.__name__)
468
+
469
+ wrapped = aw
470
+ else:
471
+
472
+ @wraps(inner)
473
+ def sw(*args: Any, **kwargs: Any) -> Any:
474
+ result = inner(*args, **kwargs)
475
+ return _check_output_size(result, max_output_chars, inner.__name__)
476
+
477
+ wrapped = sw
478
+
479
+ if update_docstring:
480
+ wrapped.__doc__ = full_doc
481
+ return wrapped # type: ignore[return-value]
422
482
 
423
- return func
483
+ if func is not None:
484
+ return decorate(func)
485
+ return decorate
424
486
 
425
487
  def list_entities(self) -> list[dict[str, Any]]:
426
488
  """
@@ -3192,4 +3192,104 @@ SlackConnectorModel: ConnectorModel = ConnectorModel(
3192
3192
  },
3193
3193
  ),
3194
3194
  ],
3195
+ search_field_paths={
3196
+ 'channel_members': ['channel_id', 'member_id'],
3197
+ 'channels': [
3198
+ 'context_team_id',
3199
+ 'created',
3200
+ 'creator',
3201
+ 'id',
3202
+ 'is_archived',
3203
+ 'is_channel',
3204
+ 'is_ext_shared',
3205
+ 'is_general',
3206
+ 'is_group',
3207
+ 'is_im',
3208
+ 'is_member',
3209
+ 'is_mpim',
3210
+ 'is_org_shared',
3211
+ 'is_pending_ext_shared',
3212
+ 'is_private',
3213
+ 'is_read_only',
3214
+ 'is_shared',
3215
+ 'last_read',
3216
+ 'locale',
3217
+ 'name',
3218
+ 'name_normalized',
3219
+ 'num_members',
3220
+ 'parent_conversation',
3221
+ 'pending_connected_team_ids',
3222
+ 'pending_connected_team_ids[]',
3223
+ 'pending_shared',
3224
+ 'pending_shared[]',
3225
+ 'previous_names',
3226
+ 'previous_names[]',
3227
+ 'purpose',
3228
+ 'purpose.creator',
3229
+ 'purpose.last_set',
3230
+ 'purpose.value',
3231
+ 'shared_team_ids',
3232
+ 'shared_team_ids[]',
3233
+ 'topic',
3234
+ 'topic.creator',
3235
+ 'topic.last_set',
3236
+ 'topic.value',
3237
+ 'unlinked',
3238
+ 'updated',
3239
+ ],
3240
+ 'users': [
3241
+ 'color',
3242
+ 'deleted',
3243
+ 'has_2fa',
3244
+ 'id',
3245
+ 'is_admin',
3246
+ 'is_app_user',
3247
+ 'is_bot',
3248
+ 'is_email_confirmed',
3249
+ 'is_forgotten',
3250
+ 'is_invited_user',
3251
+ 'is_owner',
3252
+ 'is_primary_owner',
3253
+ 'is_restricted',
3254
+ 'is_ultra_restricted',
3255
+ 'name',
3256
+ 'profile',
3257
+ 'profile.always_active',
3258
+ 'profile.avatar_hash',
3259
+ 'profile.display_name',
3260
+ 'profile.display_name_normalized',
3261
+ 'profile.email',
3262
+ 'profile.fields',
3263
+ 'profile.first_name',
3264
+ 'profile.huddle_state',
3265
+ 'profile.image_1024',
3266
+ 'profile.image_192',
3267
+ 'profile.image_24',
3268
+ 'profile.image_32',
3269
+ 'profile.image_48',
3270
+ 'profile.image_512',
3271
+ 'profile.image_72',
3272
+ 'profile.image_original',
3273
+ 'profile.last_name',
3274
+ 'profile.phone',
3275
+ 'profile.real_name',
3276
+ 'profile.real_name_normalized',
3277
+ 'profile.skype',
3278
+ 'profile.status_emoji',
3279
+ 'profile.status_emoji_display_info',
3280
+ 'profile.status_emoji_display_info[]',
3281
+ 'profile.status_expiration',
3282
+ 'profile.status_text',
3283
+ 'profile.status_text_canonical',
3284
+ 'profile.team',
3285
+ 'profile.title',
3286
+ 'real_name',
3287
+ 'team_id',
3288
+ 'tz',
3289
+ 'tz_label',
3290
+ 'tz_offset',
3291
+ 'updated',
3292
+ 'who_can_share_contact_card',
3293
+ ],
3294
+ },
3195
3295
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: airbyte-agent-slack
3
- Version: 0.1.21
3
+ Version: 0.1.22
4
4
  Summary: Airbyte Slack Connector for AI platforms
5
5
  Project-URL: Homepage, https://github.com/airbytehq/airbyte-agent-connectors
6
6
  Project-URL: Documentation, https://docs.airbyte.com/ai-agents/
@@ -104,7 +104,7 @@ connector = SlackConnector(
104
104
  )
105
105
 
106
106
  @agent.tool_plain # assumes you're using Pydantic AI
107
- @SlackConnector.describe
107
+ @SlackConnector.tool_utils
108
108
  async def slack_execute(entity: str, action: str, params: dict | None = None):
109
109
  return await connector.execute(entity, action, params or {})
110
110
  ```
@@ -125,7 +125,7 @@ connector = SlackConnector(
125
125
  )
126
126
 
127
127
  @agent.tool_plain # assumes you're using Pydantic AI
128
- @SlackConnector.describe
128
+ @SlackConnector.tool_utils
129
129
  async def slack_execute(entity: str, action: str, params: dict | None = None):
130
130
  return await connector.execute(entity, action, params or {})
131
131
  ```
@@ -158,6 +158,6 @@ For the service's official API docs, see the [Slack API reference](https://api.s
158
158
 
159
159
  ## Version information
160
160
 
161
- - **Package version:** 0.1.21
161
+ - **Package version:** 0.1.22
162
162
  - **Connector version:** 0.1.8
163
- - **Generated with Connector SDK commit SHA:** 32c5ef4692be2243558faa20132b3ece7d573aed
163
+ - **Generated with Connector SDK commit SHA:** 609c1d86c76b36ff699b57123a5a8c2050d958c3
@@ -1,20 +1,20 @@
1
1
  airbyte_agent_slack/__init__.py,sha256=XDgvYC21cFMW_s-Ecy3C5fhGMXRpRTRQR752GyzCTco,3703
2
- airbyte_agent_slack/connector.py,sha256=XHkSgykySPrGKYyFZ2BuDejVHhxfOsOJXK-B0s0Ehgk,37281
3
- airbyte_agent_slack/connector_model.py,sha256=VevhmToVmHMrLccxtEKU90n8QG7Xa6qsIB9Wns6WC54,191725
2
+ airbyte_agent_slack/connector.py,sha256=Z_HClKPXQQuYboYlUQYJ4WOqru8uwJYLNA5vPn_0UlQ,39588
3
+ airbyte_agent_slack/connector_model.py,sha256=gEtUk1vNnmnHHhNfZg34lLGzIx9YKQ1HwGKGWmqVG_k,194646
4
4
  airbyte_agent_slack/models.py,sha256=iuc7i6iuN6r6-ocKhB8-2hvPz6Qg2sJlcWfVlGTK3IA,28916
5
5
  airbyte_agent_slack/types.py,sha256=QDLOYuH23w4H_5fxVjTcBBz6BMYm2PWbuM1K_EOcD4o,32154
6
6
  airbyte_agent_slack/_vendored/__init__.py,sha256=ILl7AHXMui__swyrjxrh9yRa4dLiwBvV6axPWFWty80,38
7
7
  airbyte_agent_slack/_vendored/connector_sdk/__init__.py,sha256=T5o7roU6NSpH-lCAGZ338sE5dlh4ZU6i6IkeG1zpems,1949
8
8
  airbyte_agent_slack/_vendored/connector_sdk/auth_strategies.py,sha256=5Sb9moUp623o67Q2wMa8iZldJH08y4gQdoutoO_75Iw,42088
9
9
  airbyte_agent_slack/_vendored/connector_sdk/auth_template.py,sha256=nju4jqlFC_KI82ILNumNIyiUtRJcy7J94INIZ0QraI4,4454
10
- airbyte_agent_slack/_vendored/connector_sdk/connector_model_loader.py,sha256=SY_Juqw-cap156MsdgrMfe5MAuFdX0vUcSbH5LUYNK0,36295
10
+ airbyte_agent_slack/_vendored/connector_sdk/connector_model_loader.py,sha256=nCu6oym6LoKrS94NTivU1vyQE_qnU-rhx8ExpuOX_LU,39544
11
11
  airbyte_agent_slack/_vendored/connector_sdk/constants.py,sha256=AtzOvhDMWbRJgpsQNWl5tkogHD6mWgEY668PgRmgtOY,2737
12
12
  airbyte_agent_slack/_vendored/connector_sdk/exceptions.py,sha256=ss5MGv9eVPmsbLcLWetuu3sDmvturwfo6Pw3M37Oq5k,481
13
13
  airbyte_agent_slack/_vendored/connector_sdk/extensions.py,sha256=XWRRoJOOrwUHSKbuQt5DU7CCu8ePzhd_HuP7c_uD77w,21376
14
14
  airbyte_agent_slack/_vendored/connector_sdk/http_client.py,sha256=yucwu3OvJh5wLQa1mk-gTKjtqjKKucMw5ltmlE7mk1c,28000
15
- airbyte_agent_slack/_vendored/connector_sdk/introspection.py,sha256=2CyKXZHT74-1Id97uw1RLeyOi6TV24_hoNbQ6-6y7uI,10335
15
+ airbyte_agent_slack/_vendored/connector_sdk/introspection.py,sha256=kRVI4TDQDLdcCnTBUML8ycAtdqAQufVh-027sMkb4i8,19165
16
16
  airbyte_agent_slack/_vendored/connector_sdk/secrets.py,sha256=J9ezMu4xNnLW11xY5RCre6DHP7YMKZCqwGJfk7ufHAM,6855
17
- airbyte_agent_slack/_vendored/connector_sdk/types.py,sha256=d8PidSD5nzhSSgFwUeYtRKw8pTm0Gft_IHsGeELifuk,8748
17
+ airbyte_agent_slack/_vendored/connector_sdk/types.py,sha256=in8gHsn5nsScujOfHZmkOgNmqmJKiPyNNjg59m5fGWc,8807
18
18
  airbyte_agent_slack/_vendored/connector_sdk/utils.py,sha256=G4LUXOC2HzPoND2v4tQW68R9uuPX9NQyCjaGxb7Kpl0,1958
19
19
  airbyte_agent_slack/_vendored/connector_sdk/validation.py,sha256=4MPrxYmQh8TbCU0KdvvRKe35Lg1YYLEBd0u4aKySl_E,32122
20
20
  airbyte_agent_slack/_vendored/connector_sdk/cloud_utils/__init__.py,sha256=4799Hv9f2zxDVj1aLyQ8JpTEuFTp_oOZMRz-NZCdBJg,134
@@ -52,6 +52,6 @@ airbyte_agent_slack/_vendored/connector_sdk/telemetry/__init__.py,sha256=RaLgkBU
52
52
  airbyte_agent_slack/_vendored/connector_sdk/telemetry/config.py,sha256=tLmQwAFD0kP1WyBGWBS3ysaudN9H3e-3EopKZi6cGKg,885
53
53
  airbyte_agent_slack/_vendored/connector_sdk/telemetry/events.py,sha256=8Y1NbXiwISX-V_wRofY7PqcwEXD0dLMnntKkY6XFU2s,1328
54
54
  airbyte_agent_slack/_vendored/connector_sdk/telemetry/tracker.py,sha256=Ftrk0_ddfM7dZG8hF9xBuPwhbc9D6JZ7Q9qs5o3LEyA,5579
55
- airbyte_agent_slack-0.1.21.dist-info/METADATA,sha256=_cnBaJ-wQ9mYAo51Uk8m-PyR7SXwP7T0bsu7IoXn-DU,6672
56
- airbyte_agent_slack-0.1.21.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
57
- airbyte_agent_slack-0.1.21.dist-info/RECORD,,
55
+ airbyte_agent_slack-0.1.22.dist-info/METADATA,sha256=-9OHYI5E5whwISpCqzRxW8B3N4acr58WSOhRFioaVas,6676
56
+ airbyte_agent_slack-0.1.22.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
57
+ airbyte_agent_slack-0.1.22.dist-info/RECORD,,