glaip-sdk 0.0.1b5__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.
glaip_sdk/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ """AIP SDK - Python SDK for AI Agent Platform.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from .client import Client
8
+ from .exceptions import AIPError
9
+ from .models import MCP, Agent, Tool
10
+
11
+ __version__ = "0.1.0"
12
+ __all__ = ["Client", "Agent", "Tool", "MCP", "AIPError"]
@@ -0,0 +1,9 @@
1
+ """CLI package for AIP SDK.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from .main import main
8
+
9
+ __all__ = ["main"]
@@ -0,0 +1,5 @@
1
+ """CLI commands package."""
2
+
3
+ from . import agents, init, mcps, models, tools
4
+
5
+ __all__ = ["agents", "tools", "mcps", "models", "init"]
@@ -0,0 +1,415 @@
1
+ """Agent management commands.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ import json
8
+
9
+ import click
10
+ from rich.console import Console
11
+ from rich.panel import Panel
12
+ from rich.text import Text
13
+
14
+ from glaip_sdk.utils import is_uuid
15
+
16
+ from ..utils import (
17
+ get_client,
18
+ handle_ambiguous_resource,
19
+ output_flags,
20
+ output_list,
21
+ output_result,
22
+ safe_getattr,
23
+ )
24
+
25
+ console = Console()
26
+
27
+
28
+ @click.group(name="agents", no_args_is_help=True)
29
+ def agents_group():
30
+ """Agent management operations."""
31
+ pass
32
+
33
+
34
+ def _resolve_agent(ctx, client, ref, select=None):
35
+ """Resolve agent reference (ID or name) with ambiguity handling."""
36
+ if is_uuid(ref):
37
+ return client.agents.get_agent_by_id(ref)
38
+
39
+ # Find agents by name
40
+ matches = client.agents.find_agents(name=ref)
41
+ if not matches:
42
+ raise click.ClickException(f"Agent '{ref}' not found")
43
+
44
+ if len(matches) == 1:
45
+ return matches[0]
46
+
47
+ # Multiple matches - handle ambiguity
48
+ if select:
49
+ idx = int(select) - 1
50
+ if not (0 <= idx < len(matches)):
51
+ raise click.ClickException(f"--select must be 1..{len(matches)}")
52
+ return matches[idx]
53
+
54
+ return handle_ambiguous_resource(ctx, "agent", ref, matches)
55
+
56
+
57
+ @agents_group.command(name="list")
58
+ @output_flags()
59
+ @click.pass_context
60
+ def list_agents(ctx):
61
+ """List all agents."""
62
+ try:
63
+ client = get_client(ctx)
64
+ agents = client.agents.list_agents()
65
+
66
+ # Define table columns: (data_key, header, style, width)
67
+ columns = [
68
+ ("id", "ID", "dim", 36),
69
+ ("name", "Name", "cyan", None),
70
+ ("type", "Type", "yellow", None),
71
+ ("framework", "Framework", "blue", None),
72
+ ("version", "Version", "green", None),
73
+ ]
74
+
75
+ # Transform function for safe attribute access
76
+ def transform_agent(agent):
77
+ return {
78
+ "id": str(agent.id),
79
+ "name": agent.name,
80
+ "type": safe_getattr(agent, "type") or "N/A",
81
+ "framework": safe_getattr(agent, "framework") or "N/A",
82
+ "version": safe_getattr(agent, "version") or "N/A",
83
+ }
84
+
85
+ output_list(ctx, agents, "🤖 Available Agents", columns, transform_agent)
86
+
87
+ except Exception as e:
88
+ raise click.ClickException(str(e))
89
+
90
+
91
+ @agents_group.command()
92
+ @click.argument("agent_ref")
93
+ @click.option("--select", type=int, help="Choose among ambiguous matches (1-based)")
94
+ @output_flags()
95
+ @click.pass_context
96
+ def get(ctx, agent_ref, select):
97
+ """Get agent details."""
98
+ try:
99
+ client = get_client(ctx)
100
+
101
+ # Resolve agent with ambiguity handling
102
+ agent = _resolve_agent(ctx, client, agent_ref, select)
103
+
104
+ # Create result data with all available fields from backend
105
+ result_data = {
106
+ "id": str(getattr(agent, "id", "N/A")),
107
+ "name": getattr(agent, "name", "N/A"),
108
+ "type": getattr(agent, "type", "N/A"),
109
+ "framework": getattr(agent, "framework", "N/A"),
110
+ "version": getattr(agent, "version", "N/A"),
111
+ "description": getattr(agent, "description", "N/A"),
112
+ "instruction": getattr(agent, "instruction", "") or "-",
113
+ "metadata": getattr(agent, "metadata", "N/A"),
114
+ "language_model_id": getattr(agent, "language_model_id", "N/A"),
115
+ "agent_config": getattr(agent, "agent_config", "N/A"),
116
+ "tools": getattr(agent, "tools", []),
117
+ "agents": getattr(agent, "agents", []),
118
+ "mcps": getattr(agent, "mcps", []),
119
+ "a2a_profile": getattr(agent, "a2a_profile", "N/A"),
120
+ }
121
+
122
+ output_result(
123
+ ctx, result_data, title="Agent Details", panel_title=f"🤖 {agent.name}"
124
+ )
125
+
126
+ except Exception as e:
127
+ raise click.ClickException(str(e))
128
+
129
+
130
+ @agents_group.command()
131
+ @click.argument("agent_id")
132
+ @click.option("--input", "input_text", required=True, help="Input text for the agent")
133
+ @click.option("--chat-history", help="JSON string of chat history")
134
+ @click.option("--timeout", default=600, type=int, help="Request timeout in seconds")
135
+ @click.option(
136
+ "--view",
137
+ type=click.Choice(["rich", "plain", "json", "md"]),
138
+ default="rich",
139
+ help="Output view format",
140
+ )
141
+ @click.option(
142
+ "--compact/--verbose", default=True, help="Collapse tool steps (default: compact)"
143
+ )
144
+ @click.option(
145
+ "--save",
146
+ type=click.Path(dir_okay=False, writable=True),
147
+ help="Save transcript to file (md or json)",
148
+ )
149
+ @click.option(
150
+ "--theme", type=click.Choice(["dark", "light"]), default="dark", help="Color theme"
151
+ )
152
+ @click.option(
153
+ "--file",
154
+ "files",
155
+ multiple=True,
156
+ type=click.Path(exists=True),
157
+ help="Attach file(s)",
158
+ )
159
+ @click.pass_context
160
+ def run(
161
+ ctx,
162
+ agent_id,
163
+ input_text,
164
+ chat_history,
165
+ timeout,
166
+ view,
167
+ compact,
168
+ save,
169
+ theme,
170
+ files,
171
+ ):
172
+ """Run an agent with input text (ID required)."""
173
+ try:
174
+ client = get_client(ctx)
175
+
176
+ # Get agent by ID (no ambiguity handling needed)
177
+ try:
178
+ agent = client.agents.get_agent_by_id(agent_id)
179
+ except Exception as e:
180
+ raise click.ClickException(f"Agent with ID '{agent_id}' not found: {e}")
181
+
182
+ # Parse chat history if provided
183
+ parsed_chat_history = None
184
+ if chat_history:
185
+ try:
186
+ parsed_chat_history = json.loads(chat_history)
187
+ except json.JSONDecodeError:
188
+ raise click.ClickException("Invalid JSON in chat history")
189
+
190
+ # Always stream (no --no-stream option)
191
+ stream = ctx.obj.get("tty", True)
192
+
193
+ # Create appropriate renderer based on view
194
+ renderer = None
195
+ if stream:
196
+ from ...utils.run_renderer import RichStreamRenderer
197
+
198
+ # Use RichStreamRenderer for all streaming output
199
+ # Different view formats are handled in the output logic below
200
+ renderer = RichStreamRenderer(
201
+ console, verbose=not compact, theme=theme, use_emoji=True
202
+ )
203
+
204
+ # Run agent
205
+ result = client.agents.run_agent(
206
+ agent_id=agent.id,
207
+ message=input_text,
208
+ files=list(files),
209
+ stream=stream,
210
+ agent_name=agent.name, # Pass agent name for better display
211
+ **({"chat_history": parsed_chat_history} if parsed_chat_history else {}),
212
+ **({"timeout": timeout} if timeout else {}),
213
+ )
214
+
215
+ # Check if renderer already printed output (for streaming renderers)
216
+ printed_by_renderer = bool(renderer and stream)
217
+
218
+ # Handle output format for non-streaming or fallback
219
+ # Only print here if nothing was printed by the renderer
220
+ if not printed_by_renderer:
221
+ if (ctx.obj.get("view") == "json") or (view == "json"):
222
+ click.echo(json.dumps({"output": result}, indent=2))
223
+ elif view == "md":
224
+ click.echo(f"# Assistant\n\n{result}")
225
+ elif view == "plain":
226
+ click.echo(result)
227
+ elif not stream:
228
+ # Rich output for non-streaming
229
+ panel = Panel(
230
+ Text(result, style="green"),
231
+ title="Agent Output",
232
+ border_style="green",
233
+ )
234
+ console.print(panel)
235
+
236
+ # Save transcript if requested
237
+ if save and result:
238
+ ext = (save.rsplit(".", 1)[-1] or "").lower()
239
+ if ext == "json":
240
+ content = json.dumps({"output": result}, indent=2)
241
+ with open(save, "w", encoding="utf-8") as f:
242
+ f.write(content)
243
+ else:
244
+ content = f"# Assistant\n\n{result}\n"
245
+ with open(save, "w", encoding="utf-8") as f:
246
+ f.write(content)
247
+ console.print(f"[green]Transcript saved to: {save}[/green]")
248
+
249
+ except Exception as e:
250
+ if ctx.obj.get("view") == "json":
251
+ click.echo(json.dumps({"error": str(e)}, indent=2))
252
+ else:
253
+ console.print(f"[red]Error running agent: {e}[/red]")
254
+ raise click.ClickException(str(e))
255
+
256
+
257
+ @agents_group.command()
258
+ @click.option("--name", required=True, help="Agent name")
259
+ @click.option("--instruction", required=True, help="Agent instruction (prompt)")
260
+ @click.option("--tools", multiple=True, help="Tool names or IDs to attach")
261
+ @click.option("--agents", multiple=True, help="Sub-agent names to attach")
262
+ @click.option("--timeout", default=300, type=int, help="Execution timeout in seconds")
263
+ @output_flags()
264
+ @click.pass_context
265
+ def create(
266
+ ctx,
267
+ name,
268
+ instruction,
269
+ tools,
270
+ agents,
271
+ timeout,
272
+ ):
273
+ """Create a new agent."""
274
+ try:
275
+ client = get_client(ctx)
276
+ # Create agent (uses backend default model)
277
+ agent = client.agents.create_agent(
278
+ name=name,
279
+ instruction=instruction,
280
+ tools=list(tools),
281
+ agents=list(agents),
282
+ timeout=timeout,
283
+ )
284
+
285
+ if ctx.obj.get("view") == "json":
286
+ click.echo(json.dumps(agent.model_dump(), indent=2))
287
+ else:
288
+ # Rich output
289
+ lm = getattr(agent, "model", None)
290
+ if not lm:
291
+ cfg = getattr(agent, "agent_config", {}) or {}
292
+ lm = (
293
+ cfg.get("lm_name")
294
+ or cfg.get("model")
295
+ or "gpt-4.1 (backend default)"
296
+ )
297
+
298
+ panel = Panel(
299
+ f"[green]✅ Agent '{agent.name}' created successfully![/green]\n\n"
300
+ f"ID: {agent.id}\n"
301
+ f"Model: {lm}\n"
302
+ f"Type: {getattr(agent, 'type', 'config')}\n"
303
+ f"Framework: {getattr(agent, 'framework', 'langchain')}\n"
304
+ f"Version: {getattr(agent, 'version', '1.0')}",
305
+ title="🤖 Agent Created",
306
+ border_style="green",
307
+ )
308
+ console.print(panel)
309
+
310
+ except Exception as e:
311
+ if ctx.obj.get("view") == "json":
312
+ click.echo(json.dumps({"error": str(e)}, indent=2))
313
+ else:
314
+ console.print(f"[red]Error creating agent: {e}[/red]")
315
+ raise click.ClickException(str(e))
316
+
317
+
318
+ @agents_group.command()
319
+ @click.argument("agent_id")
320
+ @click.option("--name", help="New agent name")
321
+ @click.option("--instruction", help="New instruction")
322
+ @click.option("--tools", multiple=True, help="New tool names or IDs")
323
+ @click.option("--agents", multiple=True, help="New sub-agent names")
324
+ @click.option("--timeout", type=int, help="New timeout value")
325
+ @output_flags()
326
+ @click.pass_context
327
+ def update(ctx, agent_id, name, instruction, tools, agents, timeout):
328
+ """Update an existing agent."""
329
+ try:
330
+ client = get_client(ctx)
331
+
332
+ # Get agent by ID (no ambiguity handling needed)
333
+ try:
334
+ agent = client.agents.get_agent_by_id(agent_id)
335
+ except Exception as e:
336
+ raise click.ClickException(f"Agent with ID '{agent_id}' not found: {e}")
337
+
338
+ # Build update data
339
+ update_data = {}
340
+ if name is not None:
341
+ update_data["name"] = name
342
+ if instruction is not None:
343
+ update_data["instruction"] = instruction
344
+ if tools:
345
+ update_data["tools"] = list(tools)
346
+ if agents:
347
+ update_data["agents"] = list(agents)
348
+ if timeout is not None:
349
+ update_data["timeout"] = timeout
350
+
351
+ if not update_data:
352
+ raise click.ClickException("No update fields specified")
353
+
354
+ # Update agent
355
+ updated_agent = client.agents.update_agent(agent.id, update_data)
356
+
357
+ if ctx.obj.get("view") == "json":
358
+ click.echo(json.dumps(updated_agent.model_dump(), indent=2))
359
+ else:
360
+ console.print(
361
+ f"[green]✅ Agent '{updated_agent.name}' updated successfully[/green]"
362
+ )
363
+
364
+ except Exception as e:
365
+ if ctx.obj.get("view") == "json":
366
+ click.echo(json.dumps({"error": str(e)}, indent=2))
367
+ else:
368
+ console.print(f"[red]Error updating agent: {e}[/red]")
369
+ raise click.ClickException(str(e))
370
+
371
+
372
+ @agents_group.command()
373
+ @click.argument("agent_id")
374
+ @click.option("-y", "--yes", is_flag=True, help="Skip confirmation")
375
+ @output_flags()
376
+ @click.pass_context
377
+ def delete(ctx, agent_id, yes):
378
+ """Delete an agent."""
379
+ try:
380
+ client = get_client(ctx)
381
+
382
+ # Get agent by ID (no ambiguity handling needed)
383
+ try:
384
+ agent = client.agents.get_agent_by_id(agent_id)
385
+ except Exception as e:
386
+ raise click.ClickException(f"Agent with ID '{agent_id}' not found: {e}")
387
+
388
+ # Confirm deletion
389
+ if not yes and not click.confirm(
390
+ f"Are you sure you want to delete agent '{agent.name}'?"
391
+ ):
392
+ if ctx.obj.get("view") != "json":
393
+ console.print("Deletion cancelled.")
394
+ return
395
+
396
+ client.agents.delete_agent(agent.id)
397
+
398
+ if ctx.obj.get("view") == "json":
399
+ click.echo(
400
+ json.dumps(
401
+ {"success": True, "message": f"Agent '{agent.name}' deleted"},
402
+ indent=2,
403
+ )
404
+ )
405
+ else:
406
+ console.print(
407
+ f"[green]✅ Agent '{agent.name}' deleted successfully[/green]"
408
+ )
409
+
410
+ except Exception as e:
411
+ if ctx.obj.get("view") == "json":
412
+ click.echo(json.dumps({"error": str(e)}, indent=2))
413
+ else:
414
+ console.print(f"[red]Error deleting agent: {e}[/red]")
415
+ raise click.ClickException(str(e))