glaip-sdk 0.6.25__py3-none-any.whl → 0.7.0__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 (54) hide show
  1. glaip_sdk/cli/commands/agents/__init__.py +119 -0
  2. glaip_sdk/cli/commands/agents/_common.py +561 -0
  3. glaip_sdk/cli/commands/agents/create.py +151 -0
  4. glaip_sdk/cli/commands/agents/delete.py +64 -0
  5. glaip_sdk/cli/commands/agents/get.py +89 -0
  6. glaip_sdk/cli/commands/agents/list.py +129 -0
  7. glaip_sdk/cli/commands/agents/run.py +264 -0
  8. glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
  9. glaip_sdk/cli/commands/agents/update.py +112 -0
  10. glaip_sdk/cli/commands/mcps/__init__.py +94 -0
  11. glaip_sdk/cli/commands/mcps/_common.py +459 -0
  12. glaip_sdk/cli/commands/mcps/connect.py +82 -0
  13. glaip_sdk/cli/commands/mcps/create.py +152 -0
  14. glaip_sdk/cli/commands/mcps/delete.py +73 -0
  15. glaip_sdk/cli/commands/mcps/get.py +212 -0
  16. glaip_sdk/cli/commands/mcps/list.py +69 -0
  17. glaip_sdk/cli/commands/mcps/tools.py +235 -0
  18. glaip_sdk/cli/commands/mcps/update.py +190 -0
  19. glaip_sdk/cli/commands/shared/__init__.py +21 -0
  20. glaip_sdk/cli/commands/shared/formatters.py +91 -0
  21. glaip_sdk/cli/commands/tools/__init__.py +69 -0
  22. glaip_sdk/cli/commands/tools/_common.py +80 -0
  23. glaip_sdk/cli/commands/tools/create.py +228 -0
  24. glaip_sdk/cli/commands/tools/delete.py +61 -0
  25. glaip_sdk/cli/commands/tools/get.py +103 -0
  26. glaip_sdk/cli/commands/tools/list.py +69 -0
  27. glaip_sdk/cli/commands/tools/script.py +49 -0
  28. glaip_sdk/cli/commands/tools/update.py +102 -0
  29. glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
  30. glaip_sdk/cli/commands/transcripts/_common.py +9 -0
  31. glaip_sdk/cli/commands/transcripts/clear.py +5 -0
  32. glaip_sdk/cli/commands/transcripts/detail.py +5 -0
  33. glaip_sdk/cli/slash/tui/__init__.py +10 -1
  34. glaip_sdk/cli/slash/tui/context.py +51 -0
  35. glaip_sdk/cli/slash/tui/terminal.py +402 -0
  36. glaip_sdk/client/agents.py +1 -1
  37. glaip_sdk/client/main.py +1 -1
  38. glaip_sdk/client/mcps.py +44 -13
  39. glaip_sdk/client/payloads/agent/__init__.py +23 -0
  40. glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +22 -47
  41. glaip_sdk/client/payloads/agent/responses.py +43 -0
  42. glaip_sdk/client/tools.py +52 -23
  43. glaip_sdk/registry/tool.py +193 -81
  44. glaip_sdk/tools/base.py +41 -10
  45. glaip_sdk/utils/import_resolver.py +40 -2
  46. {glaip_sdk-0.6.25.dist-info → glaip_sdk-0.7.0.dist-info}/METADATA +2 -2
  47. {glaip_sdk-0.6.25.dist-info → glaip_sdk-0.7.0.dist-info}/RECORD +51 -18
  48. glaip_sdk/cli/commands/agents.py +0 -1502
  49. glaip_sdk/cli/commands/mcps.py +0 -1355
  50. glaip_sdk/cli/commands/tools.py +0 -575
  51. /glaip_sdk/cli/commands/{transcripts.py → transcripts_original.py} +0 -0
  52. {glaip_sdk-0.6.25.dist-info → glaip_sdk-0.7.0.dist-info}/WHEEL +0 -0
  53. {glaip_sdk-0.6.25.dist-info → glaip_sdk-0.7.0.dist-info}/entry_points.txt +0 -0
  54. {glaip_sdk-0.6.25.dist-info → glaip_sdk-0.7.0.dist-info}/top_level.txt +0 -0
glaip_sdk/client/tools.py CHANGED
@@ -103,6 +103,9 @@ class ToolClient(BaseClient):
103
103
  def _prepare_upload_data(self, name: str, framework: str, description: str | None = None, **kwargs) -> dict:
104
104
  """Prepare upload data dictionary.
105
105
 
106
+ Uses the same payload building logic as _build_create_payload to ensure
107
+ consistency between upload and metadata-only tool creation.
108
+
106
109
  Args:
107
110
  name: Tool name
108
111
  framework: Tool framework
@@ -112,28 +115,19 @@ class ToolClient(BaseClient):
112
115
  Returns:
113
116
  dict: Upload data dictionary
114
117
  """
115
- data = {
116
- "name": name,
117
- "framework": framework,
118
- "type": kwargs.pop("tool_type", DEFAULT_TOOL_TYPE), # Default to custom
119
- }
120
-
121
- if description:
122
- data["description"] = description
123
-
124
- # Handle tags if provided in kwargs
125
- if kwargs.get("tags"):
126
- if isinstance(kwargs["tags"], list):
127
- data["tags"] = ",".join(kwargs["tags"])
128
- else:
129
- data["tags"] = kwargs["tags"]
118
+ # Extract tool_type from kwargs if present, defaulting to DEFAULT_TOOL_TYPE
119
+ tool_type = kwargs.pop("tool_type", DEFAULT_TOOL_TYPE)
130
120
 
131
- # Include any other kwargs in the upload data
132
- for key, value in kwargs.items():
133
- if key not in ["tags"]: # tags already handled above
134
- data[key] = value
121
+ # Use _build_create_payload to build the payload consistently
122
+ payload = self._build_create_payload(
123
+ name=name,
124
+ description=description,
125
+ framework=framework,
126
+ tool_type=tool_type,
127
+ **kwargs,
128
+ )
135
129
 
136
- return data
130
+ return payload
137
131
 
138
132
  def _upload_tool_file(self, file_path: str, upload_data: dict) -> Tool:
139
133
  """Upload tool file to server.
@@ -439,9 +433,44 @@ class ToolClient(BaseClient):
439
433
  except OSError:
440
434
  pass # Ignore cleanup errors
441
435
 
442
- def update_tool(self, tool_id: str, **kwargs) -> Tool:
443
- """Update an existing tool."""
444
- data = self._request("PUT", f"{TOOLS_ENDPOINT}{tool_id}", json=kwargs)
436
+ def update_tool(self, tool_id: str | Tool, **kwargs) -> Tool:
437
+ """Update an existing tool.
438
+
439
+ Notes:
440
+ - Payload construction is centralized via ``_build_update_payload`` to keep metadata
441
+ update and upload update flows consistent.
442
+ - Accepts either a tool ID or a ``Tool`` instance (avoids an extra fetch when callers
443
+ already have the current tool).
444
+ """
445
+ # Backward-compatible: allow passing a Tool instance to avoid an extra fetch.
446
+ if isinstance(tool_id, Tool):
447
+ current_tool = tool_id
448
+ if not current_tool.id:
449
+ raise ValueError("Tool instance has no id; cannot update.")
450
+ tool_id_value = str(current_tool.id)
451
+ else:
452
+ current_tool = None
453
+ tool_id_value = tool_id
454
+
455
+ if not kwargs:
456
+ data = self._request("PUT", f"{TOOLS_ENDPOINT}{tool_id_value}", json={})
457
+ response = ToolResponse(**data)
458
+ return Tool.from_response(response, client=self)
459
+
460
+ if current_tool is None:
461
+ current_tool = self.get_tool_by_id(tool_id_value)
462
+
463
+ payload_kwargs = kwargs.copy()
464
+ name = payload_kwargs.pop("name", None)
465
+ description = payload_kwargs.pop("description", None)
466
+ update_payload = self._build_update_payload(
467
+ current_tool=current_tool,
468
+ name=name,
469
+ description=description,
470
+ **payload_kwargs,
471
+ )
472
+
473
+ data = self._request("PUT", f"{TOOLS_ENDPOINT}{tool_id_value}", json=update_payload)
445
474
  response = ToolResponse(**data)
446
475
  return Tool.from_response(response, client=self)
447
476
 
@@ -54,6 +54,32 @@ class ToolRegistry(BaseRegistry["Tool"]):
54
54
  value = getattr(obj, attr, None)
55
55
  return value if isinstance(value, str) else None
56
56
 
57
+ def _extract_name_from_instance(self, ref: Any) -> str | None:
58
+ """Extract name from a non-type instance.
59
+
60
+ Args:
61
+ ref: The instance to extract name from.
62
+
63
+ Returns:
64
+ The extracted name, or None if not found.
65
+ """
66
+ if isinstance(ref, type):
67
+ return None
68
+ return self._get_string_attr(ref, "name")
69
+
70
+ def _extract_name_from_class(self, ref: Any) -> str | None:
71
+ """Extract name from a class.
72
+
73
+ Args:
74
+ ref: The class to extract name from.
75
+
76
+ Returns:
77
+ The extracted name, or None if not found.
78
+ """
79
+ if not isinstance(ref, type):
80
+ return None
81
+ return self._get_string_attr(ref, "name") or self._get_name_from_model_fields(ref)
82
+
57
83
  def _extract_name(self, ref: Any) -> str:
58
84
  """Extract tool name from a reference.
59
85
 
@@ -81,31 +107,37 @@ class ToolRegistry(BaseRegistry["Tool"]):
81
107
  return ref.get("name") or ref.get("id") or ""
82
108
 
83
109
  # Tool instance (not a class) with name attribute
84
- if not isinstance(ref, type):
85
- name = self._get_string_attr(ref, "name")
86
- if name:
87
- return name
110
+ name = self._extract_name_from_instance(ref)
111
+ if name:
112
+ return name
88
113
 
89
114
  # Tool class - try direct attribute first, then model_fields
90
- if isinstance(ref, type):
91
- name = self._get_string_attr(ref, "name") or self._get_name_from_model_fields(ref)
92
- if name:
93
- return name
115
+ name = self._extract_name_from_class(ref)
116
+ if name:
117
+ return name
94
118
 
95
119
  raise ValueError(f"Cannot extract name from: {ref}")
96
120
 
97
- def _resolve_and_cache(self, ref: Any, name: str) -> Tool:
98
- """Resolve tool reference - upload if class, find if string/native.
121
+ def _cache_tool(self, tool: Tool, name: str) -> None:
122
+ """Cache a tool by name and ID if available.
99
123
 
100
124
  Args:
101
- ref: The tool reference to resolve.
125
+ tool: The tool to cache.
126
+ name: The tool name.
127
+ """
128
+ self._cache[name] = tool
129
+ if hasattr(tool, "id") and tool.id:
130
+ self._cache[tool.id] = tool
131
+
132
+ def _resolve_tool_instance(self, ref: Any, name: str) -> Tool | None:
133
+ """Resolve a ToolClass instance.
134
+
135
+ Args:
136
+ ref: The ToolClass instance to resolve.
102
137
  name: The extracted tool name.
103
138
 
104
139
  Returns:
105
- The resolved glaip_sdk.models.Tool object.
106
-
107
- Raises:
108
- ValueError: If the tool cannot be resolved.
140
+ The resolved tool, or None if not a ToolClass instance.
109
141
  """
110
142
  # Lazy imports to avoid circular dependency
111
143
  from glaip_sdk.tools.base import Tool as ToolClass # noqa: PLC0415
@@ -113,56 +145,23 @@ class ToolRegistry(BaseRegistry["Tool"]):
113
145
  from glaip_sdk.utils.discovery import find_tool # noqa: PLC0415
114
146
  from glaip_sdk.utils.sync import update_or_create_tool # noqa: PLC0415
115
147
 
116
- # Tool instance from Tool.from_native() or Tool.from_langchain()
117
148
  # Use try/except to handle mocked Tool class in tests
118
149
  try:
119
150
  is_tool_instance = isinstance(ref, ToolClass)
120
151
  except TypeError:
121
- is_tool_instance = False
122
-
123
- if is_tool_instance:
124
- # If Tool has an ID, it's already deployed - return as-is
125
- if ref.id is not None:
126
- logger.debug("Caching already deployed tool: %s", name)
127
- self._cache[name] = ref
128
- # Also cache by id for consistency with other resolution branches
129
- self._cache[ref.id] = ref
130
- return ref
131
-
132
- # Tool.from_native() - look up on platform
133
- if ref.tool_type == ToolType.NATIVE:
134
- logger.info("Looking up native tool: %s", name)
135
- tool = find_tool(name)
136
- if tool:
137
- self._cache[name] = tool
138
- return tool
139
- raise ValueError(f"Native tool not found on platform: {name}")
140
-
141
- # Tool.from_langchain() - upload the tool_class
142
- if ref.tool_class is not None:
143
- logger.info("Uploading custom tool: %s", name)
144
- tool = update_or_create_tool(ref.tool_class)
145
- self._cache[name] = tool
146
- if tool.id:
147
- self._cache[tool.id] = tool
148
- return tool
152
+ return None
149
153
 
150
- # Unresolvable Tool instance - neither native nor has tool_class
151
- raise ValueError(
152
- f"Cannot resolve Tool instance: {ref}. "
153
- f"Tool has no id, is not NATIVE type, and has no tool_class. "
154
- f"Ensure Tool is created via Tool.from_native() or Tool.from_langchain()."
155
- )
154
+ if not is_tool_instance:
155
+ return None
156
156
 
157
- # Already deployed tool (not a ToolClass, but has id/name) - just cache and return
158
- # This handles API response objects and backward compatibility
159
- if hasattr(ref, "id") and hasattr(ref, "name") and not isinstance(ref, type):
160
- if ref.id is not None:
161
- logger.debug("Caching already deployed tool: %s", name)
162
- self._cache[name] = ref
163
- return ref
157
+ # If Tool has an ID, it's already deployed - return as-is
158
+ if ref.id is not None:
159
+ logger.debug("Caching already deployed tool: %s", name)
160
+ self._cache_tool(ref, name)
161
+ return ref
164
162
 
165
- # Tool without ID (backward compatibility) - look up on platform
163
+ # Tool.from_native() - look up on platform
164
+ if ref.tool_type == ToolType.NATIVE:
166
165
  logger.info("Looking up native tool: %s", name)
167
166
  tool = find_tool(name)
168
167
  if tool:
@@ -170,32 +169,144 @@ class ToolRegistry(BaseRegistry["Tool"]):
170
169
  return tool
171
170
  raise ValueError(f"Native tool not found on platform: {name}")
172
171
 
173
- # Custom tool class - upload it
174
- if self._is_custom_tool(ref):
172
+ # Tool.from_langchain() - upload the tool_class
173
+ if ref.tool_class is not None:
175
174
  logger.info("Uploading custom tool: %s", name)
176
- tool = update_or_create_tool(ref)
177
- self._cache[name] = tool
178
- if tool.id:
179
- self._cache[tool.id] = tool
175
+ tool = update_or_create_tool(ref.tool_class)
176
+ self._cache_tool(tool, name)
180
177
  return tool
181
178
 
182
- # Dict from API response - use ID directly if available
183
- if isinstance(ref, dict):
184
- tool_id = ref.get("id")
185
- if tool_id:
186
- tool = ToolClass(id=tool_id, name=ref.get("name", ""))
187
- self._cache[name] = tool
188
- return tool
189
- raise ValueError(f"Tool dict missing 'id': {ref}")
179
+ # Unresolvable Tool instance - neither native nor has tool_class
180
+ raise ValueError(
181
+ f"Cannot resolve Tool instance: {ref}. "
182
+ f"Tool has no id, is not NATIVE type, and has no tool_class. "
183
+ f"Ensure Tool is created via Tool.from_native() or Tool.from_langchain()."
184
+ )
190
185
 
191
- # String name - look up on platform (could be native or existing tool)
192
- if isinstance(ref, str):
193
- logger.info("Looking up tool by name: %s", name)
194
- tool = find_tool(name)
195
- if tool:
196
- self._cache[name] = tool
197
- return tool
198
- raise ValueError(f"Tool not found on platform: {name}")
186
+ def _resolve_deployed_tool(self, ref: Any, name: str) -> Tool | None:
187
+ """Resolve an already deployed tool (has id/name attributes).
188
+
189
+ Args:
190
+ ref: The tool reference to resolve.
191
+ name: The extracted tool name.
192
+
193
+ Returns:
194
+ The resolved tool, or None if not a deployed tool.
195
+ """
196
+ from glaip_sdk.utils.discovery import find_tool # noqa: PLC0415
197
+
198
+ # Already deployed tool (not a ToolClass, but has id/name)
199
+ # This handles API response objects and backward compatibility
200
+ if not (hasattr(ref, "id") and hasattr(ref, "name") and not isinstance(ref, type)):
201
+ return None
202
+
203
+ if ref.id is not None:
204
+ logger.debug("Caching already deployed tool: %s", name)
205
+ # Use _cache_tool to cache by both name and ID for consistency
206
+ self._cache_tool(ref, name)
207
+ return ref
208
+
209
+ # Tool without ID (backward compatibility) - look up on platform
210
+ logger.info("Looking up native tool: %s", name)
211
+ tool = find_tool(name)
212
+ if tool:
213
+ # Use _cache_tool to cache by both name and ID if available
214
+ self._cache_tool(tool, name)
215
+ return tool
216
+ raise ValueError(f"Native tool not found on platform: {name}")
217
+
218
+ def _resolve_custom_tool(self, ref: Any, name: str) -> Tool | None:
219
+ """Resolve a custom tool class.
220
+
221
+ Args:
222
+ ref: The tool reference to resolve.
223
+ name: The extracted tool name.
224
+
225
+ Returns:
226
+ The resolved tool, or None if not a custom tool.
227
+ """
228
+ from glaip_sdk.utils.sync import update_or_create_tool # noqa: PLC0415
229
+
230
+ if not self._is_custom_tool(ref):
231
+ return None
232
+
233
+ logger.info("Uploading custom tool: %s", name)
234
+ tool = update_or_create_tool(ref)
235
+ self._cache_tool(tool, name)
236
+ return tool
237
+
238
+ def _resolve_dict_tool(self, ref: Any, name: str) -> Tool | None:
239
+ """Resolve a tool from a dict (API response).
240
+
241
+ Args:
242
+ ref: The dict to resolve.
243
+ name: The extracted tool name.
244
+
245
+ Returns:
246
+ The resolved tool, or None if not a dict.
247
+ """
248
+ from glaip_sdk.tools.base import Tool as ToolClass # noqa: PLC0415
249
+
250
+ if not isinstance(ref, dict):
251
+ return None
252
+
253
+ tool_id = ref.get("id")
254
+ if tool_id:
255
+ tool = ToolClass(id=tool_id, name=ref.get("name", ""))
256
+ # Use _cache_tool to cache by both name and ID for consistency
257
+ self._cache_tool(tool, name)
258
+ return tool
259
+ raise ValueError(f"Tool dict missing 'id': {ref}")
260
+
261
+ def _resolve_string_tool(self, ref: Any, name: str) -> Tool | None:
262
+ """Resolve a tool from a string name.
263
+
264
+ Args:
265
+ ref: The string to resolve.
266
+ name: The extracted tool name.
267
+
268
+ Returns:
269
+ The resolved tool, or None if not a string.
270
+ """
271
+ from glaip_sdk.utils.discovery import find_tool # noqa: PLC0415
272
+
273
+ if not isinstance(ref, str):
274
+ return None
275
+
276
+ logger.info("Looking up tool by name: %s", name)
277
+ tool = find_tool(name)
278
+ if tool:
279
+ # Use _cache_tool to cache by both name and ID for consistency
280
+ self._cache_tool(tool, name)
281
+ return tool
282
+ raise ValueError(f"Tool not found on platform: {name}")
283
+
284
+ def _resolve_and_cache(self, ref: Any, name: str) -> Tool:
285
+ """Resolve tool reference - upload if class, find if string/native.
286
+
287
+ Args:
288
+ ref: The tool reference to resolve.
289
+ name: The extracted tool name.
290
+
291
+ Returns:
292
+ The resolved glaip_sdk.models.Tool object.
293
+
294
+ Raises:
295
+ ValueError: If the tool cannot be resolved.
296
+ """
297
+ # Try each resolution strategy in order
298
+ resolvers = [
299
+ self._resolve_tool_instance,
300
+ self._resolve_deployed_tool,
301
+ self._resolve_custom_tool,
302
+ self._resolve_dict_tool,
303
+ self._resolve_string_tool,
304
+ ]
305
+
306
+ for resolver in resolvers:
307
+ result = resolver(ref, name)
308
+ if result is not None:
309
+ return result
199
310
 
200
311
  raise ValueError(f"Could not resolve tool reference: {ref}")
201
312
 
@@ -234,7 +345,8 @@ class ToolRegistry(BaseRegistry["Tool"]):
234
345
  if ref.id is not None:
235
346
  name = self._extract_name(ref)
236
347
  if name not in self._cache:
237
- self._cache[name] = ref
348
+ # Use _cache_tool to cache by both name and ID for consistency
349
+ self._cache_tool(ref, name)
238
350
  return ref
239
351
 
240
352
  # Tool without ID (e.g., from Tool.from_native()) - needs platform lookup
glaip_sdk/tools/base.py CHANGED
@@ -256,6 +256,9 @@ class Tool:
256
256
  Returns:
257
257
  A Tool reference that will be uploaded during Agent.deploy().
258
258
 
259
+ Raises:
260
+ ValueError: If the tool class has no valid string 'name' attribute or field.
261
+
259
262
  Example:
260
263
  >>> from langchain_core.tools import BaseTool
261
264
  >>>
@@ -267,7 +270,42 @@ class Tool:
267
270
  >>>
268
271
  >>> greeting_tool = Tool.from_langchain(GreetingTool)
269
272
  """
270
- return cls(tool_class=tool_class, type=ToolType.CUSTOM)
273
+ # Extract name from tool_class to populate the name attribute
274
+ tool_name = cls._extract_tool_name(tool_class)
275
+ return cls(name=tool_name, tool_class=tool_class, type=ToolType.CUSTOM)
276
+
277
+ @staticmethod
278
+ def _extract_tool_name(tool_class: type) -> str:
279
+ """Extract tool name from a LangChain tool class.
280
+
281
+ Args:
282
+ tool_class: A LangChain BaseTool subclass.
283
+
284
+ Returns:
285
+ The extracted tool name.
286
+
287
+ Raises:
288
+ ValueError: If name cannot be extracted or is not a valid string.
289
+ """
290
+ # Try model_fields first (Pydantic v2)
291
+ if hasattr(tool_class, "model_fields"):
292
+ name_field = tool_class.model_fields.get("name")
293
+ if name_field and name_field.default:
294
+ # Validate that default is actually a string
295
+ if isinstance(name_field.default, str):
296
+ return name_field.default
297
+
298
+ # Try direct name attribute
299
+ if hasattr(tool_class, "name"):
300
+ name_attr = getattr(tool_class, "name")
301
+ if isinstance(name_attr, str):
302
+ return name_attr
303
+
304
+ # If we can't extract the name, raise an error
305
+ raise ValueError(
306
+ f"Cannot extract name from tool class {tool_class.__name__}. "
307
+ f"Ensure the tool class has a 'name' attribute or field with a valid string value."
308
+ )
271
309
 
272
310
  def get_import_path(self) -> str | None:
273
311
  """Get the import path for custom tools.
@@ -292,15 +330,8 @@ class Tool:
292
330
  return self.name
293
331
 
294
332
  if self.tool_class is not None:
295
- # LangChain BaseTool - get name from model_fields
296
- if hasattr(self.tool_class, "model_fields"):
297
- name_field = self.tool_class.model_fields.get("name")
298
- if name_field and name_field.default:
299
- return name_field.default
300
-
301
- # Direct name attribute
302
- if hasattr(self.tool_class, "name"):
303
- return self.tool_class.name
333
+ # Reuse extraction logic for consistency
334
+ return self._extract_tool_name(self.tool_class)
304
335
 
305
336
  raise ValueError(f"Cannot determine name for tool: {self}")
306
337
 
@@ -215,11 +215,49 @@ class ImportResolver:
215
215
  True if import should be skipped.
216
216
  """
217
217
  if isinstance(node, ast.ImportFrom):
218
- return node.module and any(node.module.startswith(m) for m in self.EXCLUDED_MODULES)
218
+ return self._should_skip_import_from(node)
219
219
  if isinstance(node, ast.Import):
220
- return any(alias.name.startswith(m) for m in self.EXCLUDED_MODULES for alias in node.names)
220
+ return self._should_skip_regular_import(node)
221
221
  return False
222
222
 
223
+ def _should_skip_import_from(self, node: ast.ImportFrom) -> bool:
224
+ """Check if ImportFrom node should be skipped.
225
+
226
+ Args:
227
+ node: ImportFrom node to check.
228
+
229
+ Returns:
230
+ True if import should be skipped.
231
+ """
232
+ if not node.module:
233
+ return False
234
+ return self._is_module_excluded(node.module)
235
+
236
+ def _should_skip_regular_import(self, node: ast.Import) -> bool:
237
+ """Check if Import node should be skipped.
238
+
239
+ Args:
240
+ node: Import node to check.
241
+
242
+ Returns:
243
+ True if any alias should be skipped.
244
+ """
245
+ return any(self._is_module_excluded(alias.name) for alias in node.names)
246
+
247
+ def _is_module_excluded(self, module_name: str) -> bool:
248
+ """Check if a module name should be excluded.
249
+
250
+ Args:
251
+ module_name: Module name to check.
252
+
253
+ Returns:
254
+ True if module is excluded.
255
+ """
256
+ # Exact match for glaip_sdk or match excluded submodules with boundary
257
+ if module_name == "glaip_sdk":
258
+ return True
259
+ return any(module_name == m or module_name.startswith(m + ".") for m in self.EXCLUDED_MODULES)
260
+
223
261
  @staticmethod
224
262
  def _build_import_strings(future_imports: list, regular_imports: list) -> list[str]:
225
263
  """Build formatted import strings from import nodes.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: glaip-sdk
3
- Version: 0.6.25
3
+ Version: 0.7.0
4
4
  Summary: Python SDK and CLI for GL AIP (GDP Labs AI Agent Package) - Build, run, and manage AI agents
5
5
  Author-email: Raymond Christopher <raymond.christopher@gdplabs.id>
6
6
  License: MIT
@@ -144,7 +144,7 @@ print("--- Stream complete ---")
144
144
 
145
145
  🎉 **SDK Success!** You're now ready to build AI-powered applications with Python.
146
146
 
147
- ---
147
+ ______________________________________________________________________
148
148
 
149
149
  ## 💻 Hello World - CLI
150
150