agno 2.4.6__py3-none-any.whl → 2.4.8__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.
Files changed (51) hide show
  1. agno/agent/agent.py +5 -1
  2. agno/db/base.py +2 -0
  3. agno/db/postgres/postgres.py +5 -5
  4. agno/db/singlestore/singlestore.py +4 -5
  5. agno/db/sqlite/sqlite.py +4 -4
  6. agno/knowledge/embedder/aws_bedrock.py +325 -106
  7. agno/knowledge/knowledge.py +83 -1853
  8. agno/knowledge/loaders/__init__.py +29 -0
  9. agno/knowledge/loaders/azure_blob.py +423 -0
  10. agno/knowledge/loaders/base.py +187 -0
  11. agno/knowledge/loaders/gcs.py +267 -0
  12. agno/knowledge/loaders/github.py +415 -0
  13. agno/knowledge/loaders/s3.py +281 -0
  14. agno/knowledge/loaders/sharepoint.py +439 -0
  15. agno/knowledge/reader/website_reader.py +2 -2
  16. agno/knowledge/remote_knowledge.py +151 -0
  17. agno/knowledge/reranker/aws_bedrock.py +299 -0
  18. agno/learn/machine.py +5 -6
  19. agno/learn/stores/session_context.py +10 -2
  20. agno/models/azure/openai_chat.py +6 -11
  21. agno/models/neosantara/__init__.py +5 -0
  22. agno/models/neosantara/neosantara.py +42 -0
  23. agno/models/utils.py +5 -0
  24. agno/os/app.py +4 -1
  25. agno/os/interfaces/agui/router.py +1 -1
  26. agno/os/routers/components/components.py +2 -0
  27. agno/os/routers/knowledge/knowledge.py +0 -1
  28. agno/os/routers/registry/registry.py +340 -192
  29. agno/os/routers/workflows/router.py +7 -1
  30. agno/os/schema.py +104 -0
  31. agno/registry/registry.py +4 -0
  32. agno/run/workflow.py +3 -0
  33. agno/session/workflow.py +1 -1
  34. agno/skills/utils.py +100 -2
  35. agno/team/team.py +6 -3
  36. agno/tools/mcp/mcp.py +26 -1
  37. agno/vectordb/lancedb/lance_db.py +22 -7
  38. agno/workflow/__init__.py +4 -0
  39. agno/workflow/cel.py +299 -0
  40. agno/workflow/condition.py +280 -58
  41. agno/workflow/loop.py +177 -46
  42. agno/workflow/parallel.py +75 -4
  43. agno/workflow/router.py +260 -44
  44. agno/workflow/step.py +14 -7
  45. agno/workflow/steps.py +43 -0
  46. agno/workflow/workflow.py +104 -46
  47. {agno-2.4.6.dist-info → agno-2.4.8.dist-info}/METADATA +25 -37
  48. {agno-2.4.6.dist-info → agno-2.4.8.dist-info}/RECORD +51 -39
  49. {agno-2.4.6.dist-info → agno-2.4.8.dist-info}/WHEEL +0 -0
  50. {agno-2.4.6.dist-info → agno-2.4.8.dist-info}/licenses/LICENSE +0 -0
  51. {agno-2.4.6.dist-info → agno-2.4.8.dist-info}/top_level.txt +0 -0
@@ -1,18 +1,27 @@
1
+ import inspect
1
2
  import time
2
- from typing import Any, Dict, List, Literal, Optional
3
+ from typing import Any, Dict, List, Optional
3
4
 
4
5
  from fastapi import APIRouter, Depends, HTTPException, Query
5
- from pydantic import BaseModel, Field
6
6
 
7
7
  from agno.os.auth import get_authentication_dependency
8
8
  from agno.os.schema import (
9
9
  BadRequestResponse,
10
+ CallableMetadata,
11
+ DbMetadata,
12
+ FunctionMetadata,
10
13
  InternalServerErrorResponse,
14
+ ModelMetadata,
11
15
  NotFoundResponse,
12
16
  PaginatedResponse,
13
17
  PaginationInfo,
18
+ RegistryContentResponse,
19
+ RegistryResourceType,
20
+ SchemaMetadata,
21
+ ToolMetadata,
14
22
  UnauthenticatedResponse,
15
23
  ValidationErrorResponse,
24
+ VectorDbMetadata,
16
25
  )
17
26
  from agno.os.settings import AgnoAPISettings
18
27
  from agno.registry import Registry
@@ -20,29 +29,6 @@ from agno.tools.function import Function
20
29
  from agno.tools.toolkit import Toolkit
21
30
  from agno.utils.log import log_error
22
31
 
23
- ComponentType = Literal["tool", "toolkit", "model", "db", "vector_db", "schema"]
24
-
25
-
26
- # ============================================
27
- # Response Schema
28
- # ============================================
29
-
30
-
31
- class ComponentResponse(BaseModel):
32
- name: str
33
- component_type: ComponentType
34
- description: Optional[str] = None
35
- metadata: Dict[str, Any] = Field(default_factory=dict)
36
- # Tool-specific fields (matching config format)
37
- parameters: Optional[Dict[str, Any]] = None
38
- requires_confirmation: Optional[bool] = None
39
- external_execution: Optional[bool] = None
40
-
41
-
42
- # ============================================
43
- # Router
44
- # ============================================
45
-
46
32
 
47
33
  def get_registry_router(registry: Registry, settings: AgnoAPISettings = AgnoAPISettings()) -> APIRouter:
48
34
  router = APIRouter(
@@ -78,8 +64,7 @@ def attach_routes(router: APIRouter, registry: Registry) -> APIRouter:
78
64
  return f"{cls.__module__}.{cls.__name__}"
79
65
 
80
66
  def _maybe_jsonable(value: Any) -> Any:
81
- # Best-effort: keep only data that is likely JSON serializable
82
- # If your Function.parameters is a Pydantic model or custom object, this avoids 500s.
67
+ # Keep only data that is likely JSON serializable
83
68
  if value is None:
84
69
  return None
85
70
  if isinstance(value, (str, int, float, bool)):
@@ -94,231 +79,394 @@ def attach_routes(router: APIRouter, registry: Registry) -> APIRouter:
94
79
  # Fallback to string to avoid serialization errors
95
80
  return str(value)
96
81
 
97
- def _get_all_components(include_schema: bool) -> List[ComponentResponse]:
98
- components: List[ComponentResponse] = []
82
+ def _extract_entrypoint_metadata(
83
+ entrypoint: Any,
84
+ ) -> tuple[Optional[str], Optional[str], Optional[str], Optional[str]]:
85
+ """Extract module, qualname, signature, and return annotation from an entrypoint callable."""
86
+ ep_module: Optional[str] = getattr(entrypoint, "__module__", None)
87
+ ep_qualname: Optional[str] = getattr(entrypoint, "__qualname__", None)
88
+ ep_signature: Optional[str] = None
89
+ ep_return_annotation: Optional[str] = None
90
+ try:
91
+ sig = inspect.signature(entrypoint)
92
+ ep_signature = str(sig)
93
+ if sig.return_annotation is not inspect.Signature.empty:
94
+ ep_return_annotation = str(sig.return_annotation)
95
+ except (ValueError, TypeError):
96
+ pass
97
+ return ep_module, ep_qualname, ep_signature, ep_return_annotation
98
+
99
+ def _get_callable_params(func: Any) -> Dict[str, Any]:
100
+ """Extract JSON schema-like parameters from a callable using inspect."""
101
+ try:
102
+ sig = inspect.signature(func)
103
+ properties: Dict[str, Any] = {}
104
+ required: List[str] = []
105
+
106
+ for param_name, param in sig.parameters.items():
107
+ if param_name in ("self", "cls"):
108
+ continue
109
+
110
+ prop: Dict[str, Any] = {}
111
+
112
+ # Try to map annotation to JSON schema type
113
+ if param.annotation is not inspect.Parameter.empty:
114
+ ann = param.annotation
115
+ if ann is str or ann == "str":
116
+ prop["type"] = "string"
117
+ elif ann is int or ann == "int":
118
+ prop["type"] = "integer"
119
+ elif ann is float or ann == "float":
120
+ prop["type"] = "number"
121
+ elif ann is bool or ann == "bool":
122
+ prop["type"] = "boolean"
123
+ elif ann is list or ann == "list":
124
+ prop["type"] = "array"
125
+ elif ann is dict or ann == "dict":
126
+ prop["type"] = "object"
127
+ else:
128
+ prop["type"] = "string"
129
+ prop["annotation"] = str(ann)
130
+ else:
131
+ prop["type"] = "string"
132
+
133
+ if param.default is not inspect.Parameter.empty:
134
+ prop["default"] = (
135
+ param.default if _maybe_jsonable(param.default) == param.default else str(param.default)
136
+ )
137
+ else:
138
+ required.append(param_name)
139
+
140
+ properties[param_name] = prop
141
+
142
+ return {"type": "object", "properties": properties, "required": required}
143
+ except (ValueError, TypeError):
144
+ return {"type": "object", "properties": {}, "required": []}
145
+
146
+ def _get_resources(resource_type: Optional[RegistryResourceType] = None) -> List[RegistryContentResponse]:
147
+ resources: List[RegistryContentResponse] = []
99
148
 
100
149
  # Tools
101
- for tool in getattr(registry, "tools", []) or []:
102
- if isinstance(tool, Toolkit):
103
- toolkit_name = _safe_name(tool, fallback=tool.__class__.__name__)
104
- functions = getattr(tool, "functions", {}) or {}
105
-
106
- components.append(
107
- ComponentResponse(
108
- name=toolkit_name,
109
- component_type="toolkit",
110
- description=_safe_str(getattr(tool, "description", None)),
111
- metadata={
112
- "class_path": _class_path(tool),
113
- "functions": sorted(list(functions.keys())),
114
- },
115
- )
116
- )
150
+ if resource_type is None or resource_type == RegistryResourceType.TOOL:
151
+ for tool in getattr(registry, "tools", []) or []:
152
+ if isinstance(tool, Toolkit):
153
+ toolkit_name = _safe_name(tool, fallback=tool.__class__.__name__)
154
+ functions = getattr(tool, "functions", {}) or {}
155
+
156
+ # Build full function details for each function in the toolkit
157
+ function_details: List[CallableMetadata] = []
158
+ for func in functions.values():
159
+ func_name = _safe_name(func, fallback=func.__class__.__name__)
160
+ # Check if function requires confirmation or external execution
161
+ requires_confirmation = getattr(func, "requires_confirmation", None)
162
+ external_execution = getattr(func, "external_execution", None)
163
+
164
+ # If not set on function, check toolkit settings
165
+ if requires_confirmation is None and hasattr(tool, "requires_confirmation_tools"):
166
+ requires_confirmation = func_name in (tool.requires_confirmation_tools or [])
167
+ if external_execution is None and hasattr(tool, "external_execution_required_tools"):
168
+ external_execution = func_name in (tool.external_execution_required_tools or [])
169
+
170
+ # Get parameters - ensure they're processed if needed
171
+ func_params = func.parameters
172
+ default_params = {"type": "object", "properties": {}, "required": []}
173
+ if func_params == default_params and func.entrypoint and not func.skip_entrypoint_processing:
174
+ try:
175
+ func_copy = func.model_copy(deep=False)
176
+ func_copy.process_entrypoint(strict=False)
177
+ func_params = func_copy.parameters
178
+ except Exception:
179
+ pass
180
+
181
+ # Extract callable metadata from entrypoint
182
+ func_module: Optional[str] = None
183
+ func_qualname: Optional[str] = None
184
+ func_signature: Optional[str] = None
185
+ func_return_annotation: Optional[str] = None
186
+ if func.entrypoint:
187
+ func_module, func_qualname, func_signature, func_return_annotation = (
188
+ _extract_entrypoint_metadata(func.entrypoint)
189
+ )
190
+
191
+ func_description = getattr(func, "description", None)
192
+ if func_description is None and func.entrypoint:
193
+ func_description = inspect.getdoc(func.entrypoint)
194
+
195
+ function_details.append(
196
+ CallableMetadata(
197
+ name=func_name,
198
+ description=_safe_str(func_description),
199
+ class_path=_class_path(func),
200
+ module=func_module,
201
+ qualname=func_qualname,
202
+ has_entrypoint=bool(getattr(func, "entrypoint", None)),
203
+ parameters=_maybe_jsonable(func_params),
204
+ requires_confirmation=requires_confirmation,
205
+ external_execution=external_execution,
206
+ signature=func_signature,
207
+ return_annotation=func_return_annotation,
208
+ )
209
+ )
117
210
 
118
- # Also expose individual functions within toolkit
119
- for func in functions.values():
120
- func_name = _safe_name(func, fallback=func.__class__.__name__)
121
- # Check if function requires confirmation or external execution
122
- # First check function-level settings, then toolkit-level settings
123
- requires_confirmation = getattr(func, "requires_confirmation", None)
124
- external_execution = getattr(func, "external_execution", None)
211
+ toolkit_metadata = ToolMetadata(
212
+ class_path=_class_path(tool),
213
+ is_toolkit=True,
214
+ functions=function_details,
215
+ )
216
+ resources.append(
217
+ RegistryContentResponse(
218
+ name=toolkit_name,
219
+ type=RegistryResourceType.TOOL,
220
+ description=_safe_str(getattr(tool, "description", None)),
221
+ metadata=toolkit_metadata.model_dump(exclude_none=True),
222
+ )
223
+ )
125
224
 
126
- # If not set on function, check toolkit settings
127
- if requires_confirmation is None and hasattr(tool, "requires_confirmation_tools"):
128
- requires_confirmation = func_name in (tool.requires_confirmation_tools or [])
129
- if external_execution is None and hasattr(tool, "external_execution_required_tools"):
130
- external_execution = func_name in (tool.external_execution_required_tools or [])
225
+ elif isinstance(tool, Function):
226
+ func_name = _safe_name(tool, fallback=tool.__class__.__name__)
227
+ requires_confirmation = getattr(tool, "requires_confirmation", None)
228
+ external_execution = getattr(tool, "external_execution", None)
131
229
 
132
230
  # Get parameters - ensure they're processed if needed
133
- func_params = func.parameters
231
+ func_params = tool.parameters
134
232
  # If parameters are empty/default and function has entrypoint, try to process it
135
233
  default_params = {"type": "object", "properties": {}, "required": []}
136
- if func_params == default_params and func.entrypoint and not func.skip_entrypoint_processing:
234
+ if func_params == default_params and tool.entrypoint and not tool.skip_entrypoint_processing:
137
235
  try:
138
236
  # Create a copy to avoid modifying the original
139
- func_copy = func.model_copy(deep=False)
140
- func_copy.process_entrypoint(strict=False)
141
- func_params = func_copy.parameters
237
+ tool_copy = tool.model_copy(deep=False)
238
+ tool_copy.process_entrypoint(strict=False)
239
+ func_params = tool_copy.parameters
142
240
  except Exception:
143
241
  # If processing fails, use original parameters
144
242
  pass
145
243
 
146
- components.append(
147
- ComponentResponse(
244
+ # Extract callable metadata from entrypoint
245
+ tool_module: Optional[str] = None
246
+ tool_qualname: Optional[str] = None
247
+ tool_signature: Optional[str] = None
248
+ tool_return_annotation: Optional[str] = None
249
+ if tool.entrypoint:
250
+ tool_module, tool_qualname, tool_signature, tool_return_annotation = (
251
+ _extract_entrypoint_metadata(tool.entrypoint)
252
+ )
253
+
254
+ func_tool_metadata = ToolMetadata(
255
+ class_path=_class_path(tool),
256
+ module=tool_module,
257
+ qualname=tool_qualname,
258
+ has_entrypoint=bool(getattr(tool, "entrypoint", None)),
259
+ parameters=_maybe_jsonable(func_params),
260
+ requires_confirmation=requires_confirmation,
261
+ external_execution=external_execution,
262
+ signature=tool_signature,
263
+ return_annotation=tool_return_annotation,
264
+ )
265
+ resources.append(
266
+ RegistryContentResponse(
148
267
  name=func_name,
149
- component_type="tool",
150
- description=_safe_str(getattr(func, "description", None)),
151
- parameters=_maybe_jsonable(func_params),
152
- requires_confirmation=requires_confirmation,
153
- external_execution=external_execution,
154
- metadata={
155
- "class_path": _class_path(func),
156
- "toolkit": toolkit_name,
157
- "has_entrypoint": bool(getattr(func, "entrypoint", None)),
158
- },
268
+ type=RegistryResourceType.TOOL,
269
+ description=_safe_str(getattr(tool, "description", None)),
270
+ metadata=func_tool_metadata.model_dump(exclude_none=True),
159
271
  )
160
272
  )
161
273
 
162
- elif isinstance(tool, Function):
163
- func_name = _safe_name(tool, fallback=tool.__class__.__name__)
164
- requires_confirmation = getattr(tool, "requires_confirmation", None)
165
- external_execution = getattr(tool, "external_execution", None)
274
+ elif callable(tool):
275
+ call_name = getattr(tool, "__name__", None) or tool.__class__.__name__
276
+ tool_module = getattr(tool, "__module__", "unknown")
166
277
 
167
- # Get parameters - ensure they're processed if needed
168
- func_params = tool.parameters
169
- # If parameters are empty/default and function has entrypoint, try to process it
170
- default_params = {"type": "object", "properties": {}, "required": []}
171
- if func_params == default_params and tool.entrypoint and not tool.skip_entrypoint_processing:
278
+ # Extract signature
279
+ callable_signature: Optional[str] = None
280
+ callable_return_annotation: Optional[str] = None
172
281
  try:
173
- # Create a copy to avoid modifying the original
174
- tool_copy = tool.model_copy(deep=False)
175
- tool_copy.process_entrypoint(strict=False)
176
- func_params = tool_copy.parameters
177
- except Exception:
178
- # If processing fails, use original parameters
282
+ sig = inspect.signature(tool)
283
+ callable_signature = str(sig)
284
+ if sig.return_annotation is not inspect.Signature.empty:
285
+ callable_return_annotation = str(sig.return_annotation)
286
+ except (ValueError, TypeError):
179
287
  pass
180
288
 
181
- components.append(
182
- ComponentResponse(
183
- name=func_name,
184
- component_type="tool",
185
- description=_safe_str(getattr(tool, "description", None)),
186
- parameters=_maybe_jsonable(func_params),
187
- requires_confirmation=requires_confirmation,
188
- external_execution=external_execution,
189
- metadata={
190
- "class_path": _class_path(tool),
191
- "has_entrypoint": bool(getattr(tool, "entrypoint", None)),
192
- },
289
+ callable_metadata = ToolMetadata(
290
+ class_path=f"{tool_module}.{call_name}",
291
+ module=tool_module,
292
+ qualname=getattr(tool, "__qualname__", None),
293
+ has_entrypoint=True,
294
+ parameters=_get_callable_params(tool),
295
+ requires_confirmation=None,
296
+ external_execution=None,
297
+ signature=callable_signature,
298
+ return_annotation=callable_return_annotation,
193
299
  )
194
- )
195
-
196
- elif callable(tool):
197
- call_name = getattr(tool, "__name__", None) or tool.__class__.__name__
198
- components.append(
199
- ComponentResponse(
200
- name=str(call_name),
201
- component_type="tool",
202
- description=_safe_str(getattr(tool, "__doc__", None)),
203
- metadata={"class_path": _class_path(tool)},
300
+ resources.append(
301
+ RegistryContentResponse(
302
+ name=str(call_name),
303
+ type=RegistryResourceType.TOOL,
304
+ description=_safe_str(getattr(tool, "__doc__", None)),
305
+ metadata=callable_metadata.model_dump(exclude_none=True),
306
+ )
204
307
  )
205
- )
206
308
 
207
309
  # Models
208
- for model in getattr(registry, "models", []) or []:
209
- model_name = (
210
- _safe_str(getattr(model, "id", None))
211
- or _safe_str(getattr(model, "name", None))
212
- or model.__class__.__name__
213
- )
214
- components.append(
215
- ComponentResponse(
216
- name=model_name,
217
- component_type="model",
218
- description=_safe_str(getattr(model, "description", None)),
219
- metadata={
220
- "class_path": _class_path(model),
221
- "provider": _safe_str(getattr(model, "provider", None)),
222
- "model_id": _safe_str(getattr(model, "id", None)),
223
- },
310
+ if resource_type is None or resource_type == RegistryResourceType.MODEL:
311
+ for model in getattr(registry, "models", []) or []:
312
+ model_name = (
313
+ _safe_str(getattr(model, "id", None))
314
+ or _safe_str(getattr(model, "name", None))
315
+ or model.__class__.__name__
316
+ )
317
+ model_metadata = ModelMetadata(
318
+ class_path=_class_path(model),
319
+ provider=_safe_str(getattr(model, "provider", None)),
320
+ model_id=_safe_str(getattr(model, "id", None)),
321
+ )
322
+ resources.append(
323
+ RegistryContentResponse(
324
+ name=model_name,
325
+ type=RegistryResourceType.MODEL,
326
+ description=_safe_str(getattr(model, "description", None)),
327
+ metadata=model_metadata.model_dump(exclude_none=True),
328
+ )
224
329
  )
225
- )
226
330
 
227
331
  # Databases
228
- for db in getattr(registry, "dbs", []) or []:
229
- db_name = (
230
- _safe_str(getattr(db, "name", None))
231
- or _safe_str(getattr(db, "id", None))
232
- or _safe_str(getattr(db, "table_name", None))
233
- or db.__class__.__name__
234
- )
235
- components.append(
236
- ComponentResponse(
237
- name=db_name,
238
- component_type="db",
239
- metadata={
240
- "class_path": _class_path(db),
241
- "db_id": _safe_str(getattr(db, "id", None)),
242
- "table_name": _safe_str(getattr(db, "table_name", None)),
243
- },
332
+ if resource_type is None or resource_type == RegistryResourceType.DB:
333
+ for db in getattr(registry, "dbs", []) or []:
334
+ db_name = (
335
+ _safe_str(getattr(db, "name", None)) or _safe_str(getattr(db, "id", None)) or db.__class__.__name__
336
+ )
337
+ db_metadata = DbMetadata(
338
+ class_path=_class_path(db),
339
+ db_id=_safe_str(getattr(db, "id", None)),
340
+ )
341
+ resources.append(
342
+ RegistryContentResponse(
343
+ name=db_name,
344
+ type=RegistryResourceType.DB,
345
+ description=_safe_str(getattr(db, "description", None)),
346
+ metadata=db_metadata.model_dump(exclude_none=True),
347
+ )
244
348
  )
245
- )
246
349
 
247
350
  # Vector databases
248
- for vdb in getattr(registry, "vector_dbs", []) or []:
249
- vdb_name = (
250
- _safe_str(getattr(vdb, "name", None))
251
- or _safe_str(getattr(vdb, "id", None))
252
- or _safe_str(getattr(vdb, "collection", None))
253
- or _safe_str(getattr(vdb, "table_name", None))
254
- or vdb.__class__.__name__
255
- )
256
- components.append(
257
- ComponentResponse(
258
- name=vdb_name,
259
- component_type="vector_db",
260
- metadata={
261
- "class_path": _class_path(vdb),
262
- "vector_db_id": _safe_str(getattr(vdb, "id", None)),
263
- "collection": _safe_str(getattr(vdb, "collection", None)),
264
- "table_name": _safe_str(getattr(vdb, "table_name", None)),
265
- },
351
+ if resource_type is None or resource_type == RegistryResourceType.VECTOR_DB:
352
+ for vdb in getattr(registry, "vector_dbs", []) or []:
353
+ vdb_name = (
354
+ _safe_str(getattr(vdb, "name", None))
355
+ or _safe_str(getattr(vdb, "id", None))
356
+ or _safe_str(getattr(vdb, "collection", None))
357
+ or _safe_str(getattr(vdb, "table_name", None))
358
+ or vdb.__class__.__name__
359
+ )
360
+ vdb_metadata = VectorDbMetadata(
361
+ class_path=_class_path(vdb),
362
+ vector_db_id=_safe_str(getattr(vdb, "id", None)),
363
+ collection=_safe_str(getattr(vdb, "collection", None)),
364
+ table_name=_safe_str(getattr(vdb, "table_name", None)),
365
+ )
366
+ resources.append(
367
+ RegistryContentResponse(
368
+ name=vdb_name,
369
+ type=RegistryResourceType.VECTOR_DB,
370
+ description=_safe_str(getattr(vdb, "description", None)),
371
+ metadata=vdb_metadata.model_dump(exclude_none=True),
372
+ )
266
373
  )
267
- )
268
374
 
269
375
  # Schemas
270
- for schema in getattr(registry, "schemas", []) or []:
271
- schema_name = schema.__name__
272
- meta: Dict[str, Any] = {"class_path": _class_path(schema)}
273
- if include_schema:
376
+ if resource_type is None or resource_type == RegistryResourceType.SCHEMA:
377
+ for schema in getattr(registry, "schemas", []) or []:
378
+ schema_name = schema.__name__
379
+ schema_json: Optional[Dict[str, Any]] = None
380
+ schema_error: Optional[str] = None
274
381
  try:
275
- meta["schema"] = schema.model_json_schema() if hasattr(schema, "model_json_schema") else {}
382
+ schema_json = schema.model_json_schema() if hasattr(schema, "model_json_schema") else {}
276
383
  except Exception as e:
277
- meta["schema_error"] = str(e)
384
+ schema_error = str(e)
278
385
 
279
- components.append(
280
- ComponentResponse(
281
- name=schema_name,
282
- component_type="schema",
283
- metadata=meta,
386
+ schema_metadata = SchemaMetadata(
387
+ class_path=_class_path(schema),
388
+ schema=schema_json,
389
+ schema_error=schema_error,
390
+ )
391
+ resources.append(
392
+ RegistryContentResponse(
393
+ name=schema_name,
394
+ type=RegistryResourceType.SCHEMA,
395
+ metadata=schema_metadata.model_dump(exclude_none=True, by_alias=True),
396
+ )
397
+ )
398
+
399
+ # Functions (raw callables used for workflow conditions, selectors, etc.)
400
+ if resource_type is None or resource_type == RegistryResourceType.FUNCTION:
401
+ for func in getattr(registry, "functions", []) or []:
402
+ func_name = getattr(func, "__name__", None) or "anonymous"
403
+ func_module = getattr(func, "__module__", "unknown")
404
+
405
+ # Extract signature
406
+ reg_func_signature: Optional[str] = None
407
+ reg_func_return_annotation: Optional[str] = None
408
+ try:
409
+ sig = inspect.signature(func)
410
+ reg_func_signature = str(sig)
411
+ if sig.return_annotation is not inspect.Signature.empty:
412
+ reg_func_return_annotation = str(sig.return_annotation)
413
+ except (ValueError, TypeError):
414
+ pass
415
+
416
+ func_description = _safe_str(getattr(func, "__doc__", None))
417
+ reg_func_metadata = FunctionMetadata(
418
+ name=func_name,
419
+ description=func_description,
420
+ class_path=f"{func_module}.{func_name}",
421
+ module=func_module,
422
+ qualname=getattr(func, "__qualname__", None),
423
+ has_entrypoint=True,
424
+ parameters=_get_callable_params(func),
425
+ requires_confirmation=None,
426
+ external_execution=None,
427
+ signature=reg_func_signature,
428
+ return_annotation=reg_func_return_annotation,
429
+ )
430
+ resources.append(
431
+ RegistryContentResponse(
432
+ name=func_name,
433
+ type=RegistryResourceType.FUNCTION,
434
+ description=func_description,
435
+ metadata=reg_func_metadata.model_dump(exclude_none=True),
436
+ )
284
437
  )
285
- )
286
438
 
287
439
  # Stable ordering helps pagination
288
- components.sort(key=lambda c: (c.component_type, c.name))
289
- return components
440
+ resources.sort(key=lambda r: (r.type, r.name))
441
+ return resources
290
442
 
291
443
  @router.get(
292
444
  "/registry",
293
- response_model=PaginatedResponse[ComponentResponse],
445
+ response_model=PaginatedResponse[RegistryContentResponse],
294
446
  response_model_exclude_none=True,
295
447
  status_code=200,
296
448
  operation_id="list_registry",
297
449
  summary="List Registry",
298
- description="List all components in the registry with optional filtering.",
450
+ description="List all resources in the registry with optional filtering.",
299
451
  )
300
452
  async def list_registry(
301
- component_type: Optional[ComponentType] = Query(None, description="Filter by component type"),
453
+ resource_type: Optional[RegistryResourceType] = Query(None, description="Filter by resource type"),
302
454
  name: Optional[str] = Query(None, description="Filter by name (partial match)"),
303
- include_schema: bool = Query(False, description="Include JSON schema for schema components"),
304
455
  page: int = Query(1, ge=1, description="Page number"),
305
456
  limit: int = Query(20, ge=1, le=100, description="Items per page"),
306
- ) -> PaginatedResponse[ComponentResponse]:
457
+ ) -> PaginatedResponse[RegistryContentResponse]:
307
458
  try:
308
459
  start_time_ms = time.time() * 1000
309
- components = _get_all_components(include_schema=include_schema)
310
-
311
- if component_type:
312
- components = [c for c in components if c.component_type == component_type]
460
+ resources = _get_resources(resource_type)
313
461
 
314
462
  if name:
315
463
  needle = name.lower().strip()
316
- components = [c for c in components if needle in c.name.lower()]
464
+ resources = [r for r in resources if needle in r.name.lower()]
317
465
 
318
- total_count = len(components)
466
+ total_count = len(resources)
319
467
  total_pages = (total_count + limit - 1) // limit if limit > 0 else 0
320
468
  start_idx = (page - 1) * limit
321
- paginated = components[start_idx : start_idx + limit]
469
+ paginated = resources[start_idx : start_idx + limit]
322
470
 
323
471
  return PaginatedResponse(
324
472
  data=paginated,
@@ -331,7 +479,7 @@ def attach_routes(router: APIRouter, registry: Registry) -> APIRouter:
331
479
  ),
332
480
  )
333
481
  except Exception as e:
334
- log_error(f"Error listing components: {e}")
482
+ log_error(f"Error listing registry resources: {e}")
335
483
  raise HTTPException(status_code=500, detail=str(e))
336
484
 
337
485
  return router