nvidia-nat-a2a 1.4.0a20251224__py3-none-any.whl → 1.4.0rc2__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.

Potentially problematic release.


This version of nvidia-nat-a2a might be problematic. Click here for more details.

nat/meta/pypi.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <!--
2
- SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
3
  SPDX-License-Identifier: Apache-2.0
4
4
 
5
5
  Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -0,0 +1,15 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """A2A CLI commands."""
@@ -0,0 +1,766 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ import asyncio
17
+ import json
18
+ import logging
19
+ import time
20
+
21
+ import click
22
+
23
+ from nat.cli.cli_utils.validation import validate_url
24
+ from nat.cli.commands.start import start_command
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ @click.group(name=__name__, invoke_without_command=False, help="A2A-related commands.")
30
+ def a2a_command():
31
+ """
32
+ A2A-related commands.
33
+ """
34
+ return None
35
+
36
+
37
+ # nat a2a serve: reuses the start/a2a frontend command
38
+ a2a_command.add_command(start_command.get_command(None, "a2a"), name="serve") # type: ignore
39
+
40
+ # Suppress verbose logs from httpx
41
+ logging.getLogger("httpx").setLevel(logging.WARNING)
42
+
43
+
44
+ @a2a_command.group(name="client", invoke_without_command=False, help="A2A client commands.")
45
+ def a2a_client_command():
46
+ """
47
+ A2A client commands.
48
+ """
49
+ try:
50
+ from nat.runtime.loader import PluginTypes
51
+ from nat.runtime.loader import discover_and_register_plugins
52
+ discover_and_register_plugins(PluginTypes.CONFIG_OBJECT)
53
+ except ImportError:
54
+ click.echo("[WARNING] A2A client functionality requires nvidia-nat-a2a package.", err=True)
55
+ pass
56
+
57
+
58
+ async def discover_agent(url: str, timeout: int = 30):
59
+ """Discover A2A agent and fetch AgentCard.
60
+
61
+ Args:
62
+ url: A2A agent URL
63
+ timeout: Timeout in seconds
64
+
65
+ Returns:
66
+ AgentCard object or None if failed
67
+ """
68
+ try:
69
+ from datetime import timedelta
70
+
71
+ from nat.plugins.a2a.client.client_base import A2ABaseClient
72
+
73
+ # Create client
74
+ client = A2ABaseClient(base_url=url, task_timeout=timedelta(seconds=timeout))
75
+
76
+ async with client:
77
+ agent_card = client.agent_card
78
+
79
+ if not agent_card:
80
+ raise RuntimeError(f"Failed to fetch agent card from {url}")
81
+
82
+ return agent_card
83
+
84
+ except ImportError:
85
+ click.echo(
86
+ "A2A client functionality requires nvidia-nat-a2a package. Install with: uv pip install nvidia-nat-a2a",
87
+ err=True)
88
+ return None
89
+
90
+
91
+ def format_agent_card_display(agent_card, verbose: bool = False):
92
+ """Format AgentCard for display.
93
+
94
+ Args:
95
+ agent_card: AgentCard object
96
+ verbose: Show full details
97
+ """
98
+ from rich.console import Console
99
+ from rich.panel import Panel
100
+
101
+ console = Console()
102
+
103
+ # Build content
104
+ content = []
105
+
106
+ # Basic info
107
+ content.append(f"[bold]Name:[/bold] {agent_card.name}")
108
+ content.append(f"[bold]Version:[/bold] {agent_card.version}")
109
+ content.append(f"[bold]Protocol Version:[/bold] {agent_card.protocol_version}")
110
+ content.append(f"[bold]URL:[/bold] {agent_card.url}")
111
+
112
+ # Transport
113
+ transport = agent_card.preferred_transport or "JSONRPC"
114
+ content.append(f"[bold]Transport:[/bold] {transport} (preferred)")
115
+
116
+ # Description
117
+ if agent_card.description:
118
+ content.append(f"[bold]Description:[/bold] {agent_card.description}")
119
+
120
+ content.append("") # Blank line
121
+
122
+ # Capabilities
123
+ content.append("[bold]Capabilities:[/bold]")
124
+ caps = agent_card.capabilities
125
+ if caps:
126
+ streaming = "✓" if caps.streaming else "✗"
127
+ content.append(f" {streaming} Streaming")
128
+ push = "✓" if caps.push_notifications else "✗"
129
+ content.append(f" {push} Push Notifications")
130
+ else:
131
+ content.append(" None specified")
132
+
133
+ content.append("") # Blank line
134
+
135
+ # Skills
136
+ skills = agent_card.skills
137
+ content.append(f"[bold]Skills:[/bold] ({len(skills)})")
138
+
139
+ for skill in skills:
140
+ content.append(f" • [cyan]{skill.id}[/cyan]")
141
+ if skill.name:
142
+ content.append(f" Name: {skill.name}")
143
+ content.append(f" Description: {skill.description}")
144
+ if skill.examples:
145
+ if verbose:
146
+ content.append(f" Examples: {', '.join(repr(e) for e in skill.examples)}")
147
+ else:
148
+ # Show first example in normal mode
149
+ content.append(f" Example: {repr(skill.examples[0])}")
150
+ if skill.tags:
151
+ content.append(f" Tags: {', '.join(skill.tags)}")
152
+
153
+ content.append("") # Blank line
154
+
155
+ # Input/Output modes
156
+ content.append(f"[bold]Input Modes:[/bold] {', '.join(agent_card.default_input_modes)}")
157
+ content.append(f"[bold]Output Modes:[/bold] {', '.join(agent_card.default_output_modes)}")
158
+
159
+ content.append("") # Blank line
160
+
161
+ # Auth
162
+ if agent_card.security or agent_card.security_schemes:
163
+ content.append("[bold]Auth Required:[/bold] Yes")
164
+ if verbose and agent_card.security_schemes:
165
+ content.append(f" Schemes: {', '.join(agent_card.security_schemes.keys())}")
166
+ else:
167
+ content.append("[bold]Auth Required:[/bold] None (public agent)")
168
+
169
+ # Create panel
170
+ panel = Panel("\n".join(content), title="[bold]Agent Card Discovery[/bold]", border_style="blue", padding=(1, 2))
171
+
172
+ console.print(panel)
173
+
174
+
175
+ @a2a_client_command.command(name="discover", help="Discover A2A agent and display AgentCard information.")
176
+ @click.option('--url', required=True, callback=validate_url, help='A2A agent URL (e.g., http://localhost:9999)')
177
+ @click.option('--json-output', is_flag=True, help='Output AgentCard as JSON')
178
+ @click.option('--verbose', is_flag=True, help='Show full AgentCard details')
179
+ @click.option('--save', type=click.Path(), help='Save AgentCard to file')
180
+ @click.option('--timeout', default=30, show_default=True, help='Timeout in seconds')
181
+ def a2a_client_discover(url: str, json_output: bool, verbose: bool, save: str | None, timeout: int):
182
+ """Discover A2A agent and display AgentCard information.
183
+
184
+ Connects to an A2A agent at the specified URL and fetches its AgentCard,
185
+ which contains information about the agent's capabilities, skills, and
186
+ configuration requirements.
187
+
188
+ Args:
189
+ url: A2A agent URL (e.g., http://localhost:9999)
190
+ json_output: Output as JSON instead of formatted display
191
+ verbose: Show full details including all skill information
192
+ save: Save AgentCard JSON to specified file
193
+ timeout: Timeout in seconds for agent connection
194
+
195
+ Examples:
196
+ nat a2a client discover --url http://localhost:9999
197
+ nat a2a client discover --url http://localhost:9999 --json-output
198
+ nat a2a client discover --url http://localhost:9999 --verbose
199
+ nat a2a client discover --url http://localhost:9999 --save agent-card.json
200
+ """
201
+ try:
202
+ # Discover agent
203
+ start_time = time.time()
204
+ agent_card = asyncio.run(discover_agent(url, timeout=timeout))
205
+ elapsed = time.time() - start_time
206
+
207
+ if not agent_card:
208
+ click.echo(f"[ERROR] Failed to discover agent at {url}", err=True)
209
+ return
210
+
211
+ # JSON output
212
+ if json_output:
213
+ output = agent_card.model_dump_json(indent=2)
214
+ click.echo(output)
215
+
216
+ # Save if requested
217
+ if save:
218
+ with open(save, 'w') as f:
219
+ f.write(output)
220
+ click.echo(f"\n[INFO] Saved to {save}", err=False)
221
+
222
+ else:
223
+ # Rich formatted output
224
+ format_agent_card_display(agent_card, verbose=verbose)
225
+
226
+ # Save if requested
227
+ if save:
228
+ with open(save, 'w') as f:
229
+ f.write(agent_card.model_dump_json(indent=2))
230
+ click.echo(f"\n✓ Saved AgentCard to {save}")
231
+
232
+ click.echo(f"\n✓ Discovery completed in {elapsed:.2f}s")
233
+
234
+ except Exception as e:
235
+ click.echo(f"[ERROR] {e}", err=True)
236
+ logger.error(f"Error in discover command: {e}", exc_info=True)
237
+
238
+
239
+ async def _create_bearer_token_auth(
240
+ builder,
241
+ bearer_token: str | None,
242
+ bearer_token_env: str | None,
243
+ ):
244
+ """Create bearer token auth configuration for CLI usage."""
245
+ import os
246
+
247
+ from pydantic import SecretStr
248
+
249
+ from nat.authentication.api_key.api_key_auth_provider_config import APIKeyAuthProviderConfig
250
+ from nat.data_models.authentication import HeaderAuthScheme
251
+
252
+ # Get token from env var or direct input
253
+ if bearer_token_env:
254
+ token_value = os.getenv(bearer_token_env)
255
+ if not token_value:
256
+ raise ValueError(f"Environment variable '{bearer_token_env}' not found or empty")
257
+ elif bearer_token:
258
+ token_value = bearer_token
259
+ else:
260
+ raise ValueError("No bearer token provided")
261
+
262
+ # Create API key auth config with Bearer scheme
263
+ auth_config = APIKeyAuthProviderConfig(
264
+ raw_key=SecretStr(token_value),
265
+ auth_scheme=HeaderAuthScheme.BEARER,
266
+ )
267
+
268
+ auth_provider_name = "bearer_token_cli"
269
+ await builder.add_auth_provider(auth_provider_name, auth_config)
270
+ return auth_provider_name
271
+
272
+
273
+ async def _load_auth_from_config(
274
+ builder,
275
+ config_path: str,
276
+ auth_provider_name: str,
277
+ ):
278
+ """Load auth provider from auth-only config file.
279
+
280
+ Parses only the authentication section from YAML file.
281
+ No other workflow sections are required.
282
+ """
283
+ import yaml
284
+ from pydantic import TypeAdapter
285
+
286
+ from nat.cli.type_registry import GlobalTypeRegistry
287
+ from nat.data_models.authentication import AuthProviderBaseConfig
288
+
289
+ with open(config_path) as f:
290
+ config_data = yaml.safe_load(f)
291
+
292
+ # Extract just the authentication section
293
+ if 'authentication' not in config_data:
294
+ raise ValueError("Config file must contain 'authentication' section")
295
+
296
+ auth_configs = config_data['authentication']
297
+ if auth_provider_name not in auth_configs:
298
+ raise ValueError(f"Auth provider '{auth_provider_name}' not found in config")
299
+
300
+ auth_config_dict = auth_configs[auth_provider_name]
301
+
302
+ # Parse the dictionary into the proper AuthProviderBaseConfig subclass
303
+ auth_union_type = GlobalTypeRegistry.get().compute_annotation(AuthProviderBaseConfig)
304
+ auth_config = TypeAdapter(auth_union_type).validate_python(auth_config_dict)
305
+
306
+ # Add the auth provider to builder
307
+ await builder.add_auth_provider(auth_provider_name, auth_config)
308
+ return auth_provider_name
309
+
310
+
311
+ async def _create_auth_from_json(
312
+ builder,
313
+ auth_json: str,
314
+ ):
315
+ """Create auth provider from inline JSON config."""
316
+ from pydantic import TypeAdapter
317
+
318
+ from nat.cli.type_registry import GlobalTypeRegistry
319
+ from nat.data_models.authentication import AuthProviderBaseConfig
320
+
321
+ auth_config_dict = json.loads(auth_json)
322
+
323
+ if '_type' not in auth_config_dict:
324
+ raise ValueError("Auth JSON must contain '_type' field")
325
+
326
+ # Parse the dictionary into the proper AuthProviderBaseConfig subclass
327
+ auth_union_type = GlobalTypeRegistry.get().compute_annotation(AuthProviderBaseConfig)
328
+ auth_config = TypeAdapter(auth_union_type).validate_python(auth_config_dict)
329
+
330
+ # Add the auth provider to builder
331
+ auth_provider_name = "auth_json_cli"
332
+ await builder.add_auth_provider(auth_provider_name, auth_config)
333
+ return auth_provider_name
334
+
335
+
336
+ async def get_a2a_function_group(
337
+ url: str,
338
+ timeout: int = 30,
339
+ auth_provider_name: str | None = None,
340
+ user_id: str | None = None,
341
+ ):
342
+ """Load A2A client as a function group with optional authentication.
343
+
344
+ Args:
345
+ url: A2A agent URL
346
+ timeout: Timeout in seconds
347
+ auth_provider_name: Optional auth provider name (from builder)
348
+ user_id: Optional user ID for authentication
349
+
350
+ Returns:
351
+ Tuple of (builder, group, functions dict) or (None, None, None) if failed
352
+ """
353
+ try:
354
+ from datetime import timedelta
355
+
356
+ from nat.builder.context import ContextState
357
+ from nat.builder.workflow_builder import WorkflowBuilder
358
+ from nat.plugins.a2a.client.client_config import A2AClientConfig
359
+
360
+ builder = WorkflowBuilder()
361
+ await builder.__aenter__()
362
+
363
+ # Set user_id in context before creating function group (similar to nat run)
364
+ # This is required for per-user function groups after multi-user support
365
+ if user_id is None:
366
+ user_id = "nat_a2a_cli_user_id" # Default user_id for CLI operations
367
+
368
+ context_state = ContextState()
369
+ context_state.user_id.set(user_id)
370
+ logger.debug(f"Set user_id in context: {user_id}")
371
+
372
+ # Create A2A config with optional auth
373
+ config = A2AClientConfig(
374
+ url=url,
375
+ task_timeout=timedelta(seconds=timeout),
376
+ auth_provider=auth_provider_name,
377
+ )
378
+
379
+ # Add function group
380
+ group = await builder.add_function_group("a2a_client", config)
381
+
382
+ # Get accessible functions
383
+ fns = await group.get_accessible_functions()
384
+ logger.debug(f"Available functions: {list(fns.keys())}")
385
+
386
+ return builder, group, fns
387
+
388
+ except ImportError:
389
+ click.echo(
390
+ "A2A client functionality requires nvidia-nat-a2a package. Install with: uv pip install nvidia-nat-a2a",
391
+ err=True)
392
+ return None, None, None
393
+ except Exception as e:
394
+ logger.error(f"Error loading A2A function group: {e}", exc_info=True)
395
+ raise
396
+
397
+
398
+ def format_info_display(info: dict):
399
+ """Format agent info for simple text display."""
400
+ click.secho("Agent Information", fg='cyan', bold=True)
401
+ click.echo(f" Name: {info.get('name', 'N/A')}")
402
+ click.echo(f" Version: {info.get('version', 'N/A')}")
403
+ click.echo(f" URL: {info.get('url', 'N/A')}")
404
+
405
+ if info.get('description'):
406
+ click.echo(f" Description: {info['description']}")
407
+
408
+ if info.get('provider'):
409
+ provider = info['provider']
410
+ if provider.get('name'):
411
+ click.echo(f" Provider: {provider['name']}")
412
+
413
+ caps = info.get('capabilities', {})
414
+ streaming = "✓" if caps.get('streaming') else "✗"
415
+ click.echo(f" Streaming: {streaming}")
416
+
417
+ click.echo(f" Skills: {info.get('num_skills', 0)}")
418
+
419
+
420
+ def format_skills_display(skills_data: dict):
421
+ """Format agent skills for simple text display."""
422
+ agent_name = skills_data.get('agent', 'Unknown')
423
+ skills = skills_data.get('skills', [])
424
+
425
+ click.secho(f"Agent Skills ({len(skills)})", fg='cyan', bold=True)
426
+ click.echo(f" Agent: {agent_name}")
427
+ click.echo()
428
+
429
+ for i, skill in enumerate(skills, 1):
430
+ click.secho(f" [{i}] {skill['id']}", fg='yellow')
431
+ if skill.get('name'):
432
+ click.echo(f" Name: {skill['name']}")
433
+ click.echo(f" Description: {skill['description']}")
434
+
435
+ if skill.get('examples'):
436
+ examples = skill['examples']
437
+ if len(examples) == 1:
438
+ click.echo(f" Example: {examples[0]}")
439
+ else:
440
+ click.echo(f" Examples: {examples[0]}")
441
+ if len(examples) > 1:
442
+ click.secho(f" (+{len(examples)-1} more)", fg='bright_black')
443
+
444
+ if skill.get('tags'):
445
+ click.echo(f" Tags: {', '.join(skill['tags'])}")
446
+
447
+ if i < len(skills):
448
+ click.echo() # Blank line between skills
449
+
450
+
451
+ def format_call_response_display(message: str, response: str, elapsed: float):
452
+ """Format agent call response for simple text display."""
453
+ # Show query for context
454
+ click.secho(f"Query: {message}", fg='cyan')
455
+ click.echo()
456
+
457
+ # Show response (main output)
458
+ click.echo(response)
459
+
460
+ # Show timing info in bright green to stderr
461
+ click.echo()
462
+ click.secho(f"({elapsed:.2f}s)", fg='bright_green', err=True)
463
+
464
+
465
+ @a2a_client_command.command(name="get_info", help="Get agent metadata and information.")
466
+ @click.option('--url', required=True, callback=validate_url, help='A2A agent URL (e.g., http://localhost:9999)')
467
+ @click.option('--json-output', is_flag=True, help='Output as JSON')
468
+ @click.option('--timeout', default=30, show_default=True, help='Timeout in seconds')
469
+ @click.option('--user-id', help='User ID for authentication (optional)')
470
+ def a2a_client_get_info(url: str, json_output: bool, timeout: int, user_id: str | None):
471
+ """Get agent metadata including name, version, provider, and capabilities.
472
+
473
+ This command connects to an A2A agent and retrieves its metadata.
474
+
475
+ Args:
476
+ url: A2A agent URL (e.g., http://localhost:9999)
477
+ json_output: Output as JSON instead of formatted display
478
+ timeout: Timeout in seconds for agent connection
479
+ user_id: User ID for authentication (optional)
480
+
481
+ Examples:
482
+ nat a2a client get_info --url http://localhost:9999
483
+ nat a2a client get_info --url http://localhost:9999 --json-output
484
+ nat a2a client get_info --url http://localhost:9999 --user-id alice
485
+ """
486
+
487
+ async def run():
488
+ builder = None
489
+ try:
490
+ # Load A2A function group
491
+ builder, group, fns = await get_a2a_function_group(url, timeout=timeout, user_id=user_id)
492
+ if not builder:
493
+ return
494
+
495
+ # Get the get_info function
496
+ fn = fns.get("a2a_client__get_info")
497
+ if not fn:
498
+ click.echo(f"[ERROR] get_info function not found. Available: {list(fns.keys())}", err=True)
499
+ return
500
+
501
+ # Call the function
502
+ info = await fn.acall_invoke()
503
+
504
+ if json_output:
505
+ click.echo(json.dumps(info, indent=2))
506
+ else:
507
+ format_info_display(info)
508
+
509
+ except Exception as e:
510
+ click.echo(f"[ERROR] {e}", err=True)
511
+ logger.error(f"Error in get_info command: {e}", exc_info=True)
512
+ finally:
513
+ if builder:
514
+ await builder.__aexit__(None, None, None)
515
+
516
+ asyncio.run(run())
517
+
518
+
519
+ @a2a_client_command.command(name="get_skills", help="Get agent skills and capabilities.")
520
+ @click.option('--url', required=True, callback=validate_url, help='A2A agent URL (e.g., http://localhost:9999)')
521
+ @click.option('--json-output', is_flag=True, help='Output as JSON')
522
+ @click.option('--timeout', default=30, show_default=True, help='Timeout in seconds')
523
+ @click.option('--user-id', help='User ID for authentication (optional)')
524
+ def a2a_client_get_skills(url: str, json_output: bool, timeout: int, user_id: str | None):
525
+ """Get detailed list of agent skills and capabilities.
526
+
527
+ This command connects to an A2A agent and retrieves all available skills
528
+ with their descriptions, examples, and tags.
529
+
530
+ Args:
531
+ url: A2A agent URL (e.g., http://localhost:9999)
532
+ json_output: Output as JSON instead of formatted display
533
+ timeout: Timeout in seconds for agent connection
534
+ user_id: User ID for authentication (optional)
535
+
536
+ Examples:
537
+ nat a2a client get_skills --url http://localhost:9999
538
+ nat a2a client get_skills --url http://localhost:9999 --json-output
539
+ nat a2a client get_skills --url http://localhost:9999 --user-id alice
540
+ """
541
+
542
+ async def run():
543
+ builder = None
544
+ try:
545
+ # Load A2A function group
546
+ builder, group, fns = await get_a2a_function_group(url, timeout=timeout, user_id=user_id)
547
+ if not builder:
548
+ return
549
+
550
+ # Get the get_skills function
551
+ fn = fns.get("a2a_client__get_skills")
552
+ if not fn:
553
+ click.echo(f"[ERROR] get_skills function not found. Available: {list(fns.keys())}", err=True)
554
+ return
555
+
556
+ # Call the function
557
+ skills_data = await fn.acall_invoke()
558
+
559
+ if json_output:
560
+ click.echo(json.dumps(skills_data, indent=2))
561
+ else:
562
+ format_skills_display(skills_data)
563
+
564
+ except Exception as e:
565
+ click.echo(f"[ERROR] {e}", err=True)
566
+ logger.error(f"Error in get_skills command: {e}", exc_info=True)
567
+ finally:
568
+ if builder:
569
+ await builder.__aexit__(None, None, None)
570
+
571
+ asyncio.run(run())
572
+
573
+
574
+ @a2a_client_command.command(name="call", help="Call the agent with a message.")
575
+ @click.option('--url', required=True, callback=validate_url, help='A2A agent URL (e.g., http://localhost:9999)')
576
+ @click.option('--message', required=True, help='Message to send to the agent')
577
+ @click.option('--task-id', help='Optional task ID for continuing a conversation')
578
+ @click.option('--context-id', help='Optional context ID for maintaining context')
579
+ @click.option('--json-output', is_flag=True, help='Output as JSON')
580
+ @click.option('--timeout', default=30, show_default=True, help='Timeout in seconds')
581
+ @click.option('--bearer-token', help='Bearer token for authentication')
582
+ @click.option('--bearer-token-env', help='Environment variable containing bearer token')
583
+ @click.option('--auth-config', type=click.Path(exists=True), help='Auth-only config file (YAML)')
584
+ @click.option('--auth-provider', help='Auth provider name from config')
585
+ @click.option('--auth-json', help='Inline auth provider config as JSON')
586
+ @click.option('--user-id', help='User ID for authentication (optional)')
587
+ def a2a_client_call(url: str,
588
+ message: str,
589
+ task_id: str | None,
590
+ context_id: str | None,
591
+ json_output: bool,
592
+ timeout: int,
593
+ bearer_token: str | None,
594
+ bearer_token_env: str | None,
595
+ auth_config: str | None,
596
+ auth_provider: str | None,
597
+ auth_json: str | None,
598
+ user_id: str | None):
599
+ """Call an A2A agent with a message and get a response.
600
+
601
+ This command connects to an A2A agent, sends a message, and displays the response.
602
+ Use this for one-off queries or testing. For complex workflows with multiple agents
603
+ and tools, create a NAT workflow instead.
604
+
605
+ Authentication is optional. If the agent requires authentication, use one of:
606
+ - --bearer-token or --bearer-token-env for simple token auth
607
+ - --auth-config and --auth-provider for config-based auth
608
+ - --auth-json for inline JSON auth configuration
609
+
610
+ Args:
611
+ url: A2A agent URL (e.g., http://localhost:9999)
612
+ message: Message to send to the agent
613
+ task_id: Optional task ID for continuing a conversation
614
+ context_id: Optional context ID for maintaining context
615
+ json_output: Output as JSON instead of formatted display
616
+ timeout: Timeout in seconds for agent connection
617
+ bearer_token: Bearer token for authentication
618
+ bearer_token_env: Environment variable containing bearer token
619
+ auth_config: Path to auth-only config file (YAML)
620
+ auth_provider: Auth provider name from config
621
+ auth_json: Inline auth provider config as JSON
622
+ user_id: User ID for authentication
623
+
624
+ Examples:
625
+ # Public agent (no auth)
626
+ nat a2a client call --url http://localhost:9999 --message "Hello"
627
+
628
+ # Bearer token auth
629
+ nat a2a client call --url http://localhost:9999 --message "Hello" --bearer-token "sk-abc123"
630
+
631
+ # Config-based auth
632
+ nat a2a client call --url http://localhost:9999 --message "Hello" \
633
+ --auth-config auth.yml --auth-provider my_oauth --user-id alice
634
+
635
+ # Inline JSON auth
636
+ nat a2a client call --url http://localhost:9999 --message "Hello" \
637
+ --auth-json '{"_type": "api_key", "raw_key": "sk-abc123", "auth_scheme": "Bearer"}'
638
+ """
639
+
640
+ async def run():
641
+ # Set up authentication callback for CLI workflows
642
+ # This is needed for A2A clients that authenticate during build
643
+ try:
644
+ from nat.builder.context import Context
645
+ from nat.front_ends.console.authentication_flow_handler import ConsoleAuthenticationFlowHandler
646
+
647
+ # Create and set the auth handler early so it's available during workflow building
648
+ auth_handler = ConsoleAuthenticationFlowHandler()
649
+ Context.get()._context_state.user_auth_callback.set(auth_handler.authenticate)
650
+ logger.debug("CLI authentication callback registered for A2A client call")
651
+ except ImportError:
652
+ # Console auth handler not available, skip auth handler setup
653
+ logger.debug("Console authentication handler not available, skipping CLI authentication callback setup")
654
+
655
+ builder = None
656
+ try:
657
+ # Validate auth options
658
+ auth_methods = sum([bool(bearer_token or bearer_token_env), bool(auth_config), bool(auth_json)])
659
+
660
+ if auth_methods > 1:
661
+ click.echo("[ERROR] Use only one authentication method", err=True)
662
+ return
663
+
664
+ if auth_provider and not auth_config:
665
+ click.echo("[ERROR] --auth-provider requires --auth-config", err=True)
666
+ return
667
+
668
+ # Setup authentication if provided
669
+ auth_provider_name = None
670
+ if bearer_token or bearer_token_env:
671
+ # Bearer token auth
672
+ from nat.builder.workflow_builder import WorkflowBuilder
673
+ builder = WorkflowBuilder()
674
+ await builder.__aenter__()
675
+
676
+ try:
677
+ auth_provider_name = await _create_bearer_token_auth(builder, bearer_token, bearer_token_env)
678
+ except Exception as e:
679
+ click.echo(f"[ERROR] Failed to configure bearer token authentication: {e}", err=True)
680
+ return
681
+
682
+ elif auth_config:
683
+ # Config-based auth
684
+ from nat.builder.workflow_builder import WorkflowBuilder
685
+ builder = WorkflowBuilder()
686
+ await builder.__aenter__()
687
+
688
+ try:
689
+ if not auth_provider:
690
+ click.echo("[ERROR] --auth-provider is required with --auth-config", err=True)
691
+ return
692
+ auth_provider_name = await _load_auth_from_config(builder, auth_config, auth_provider)
693
+ except Exception as e:
694
+ click.echo(f"[ERROR] Failed to load auth from config: {e}", err=True)
695
+ return
696
+
697
+ elif auth_json:
698
+ # Inline JSON auth
699
+ from nat.builder.workflow_builder import WorkflowBuilder
700
+ builder = WorkflowBuilder()
701
+ await builder.__aenter__()
702
+
703
+ try:
704
+ auth_provider_name = await _create_auth_from_json(builder, auth_json)
705
+ except Exception as e:
706
+ click.echo(f"[ERROR] Failed to parse auth JSON: {e}", err=True)
707
+ return
708
+
709
+ # Load A2A function group (with or without auth)
710
+ start_time = time.time()
711
+
712
+ if builder:
713
+ # Auth was configured, use existing builder
714
+ from datetime import timedelta
715
+
716
+ from nat.builder.context import ContextState
717
+ from nat.plugins.a2a.client.client_config import A2AClientConfig
718
+
719
+ # Set user_id in context before creating function group (similar to nat run)
720
+ # This is required for per-user function groups after multi-user support
721
+ resolved_user_id = user_id if user_id else "nat_a2a_cli_user_id"
722
+ context_state = ContextState()
723
+ context_state.user_id.set(resolved_user_id)
724
+ logger.debug(f"Set user_id in context: {resolved_user_id}")
725
+
726
+ config = A2AClientConfig(
727
+ url=url,
728
+ task_timeout=timedelta(seconds=timeout),
729
+ auth_provider=auth_provider_name,
730
+ )
731
+ group = await builder.add_function_group("a2a_client", config)
732
+ fns = await group.get_accessible_functions()
733
+ else:
734
+ # No auth, use helper function
735
+ builder, group, fns = await get_a2a_function_group(url, timeout=timeout, user_id=user_id)
736
+ if not builder:
737
+ return
738
+
739
+ # Get the call function
740
+ fn = fns.get("a2a_client__call")
741
+ if not fn:
742
+ click.echo(f"[ERROR] call function not found. Available: {list(fns.keys())}", err=True)
743
+ return
744
+
745
+ # Call the agent with the message
746
+ response = await fn.acall_invoke(query=message, task_id=task_id, context_id=context_id)
747
+ elapsed = time.time() - start_time
748
+
749
+ if json_output:
750
+ result = {"message": message, "response": response, "elapsed": elapsed}
751
+ if task_id:
752
+ result["task_id"] = task_id
753
+ if context_id:
754
+ result["context_id"] = context_id
755
+ click.echo(json.dumps(result, indent=2))
756
+ else:
757
+ format_call_response_display(message, response, elapsed)
758
+
759
+ except Exception as e:
760
+ click.echo(f"[ERROR] {e}", err=True)
761
+ logger.error(f"Error in call command: {e}", exc_info=True)
762
+ finally:
763
+ if builder:
764
+ await builder.__aexit__(None, None, None)
765
+
766
+ asyncio.run(run())
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -175,7 +175,7 @@ class A2AFrontEndPluginWorker:
175
175
 
176
176
  for function_name, function in functions.items():
177
177
  # Create skill from function metadata
178
- skill_name = function_name.replace('_', ' ').replace('.', ' - ').title()
178
+ skill_name = function_name.replace('__', ' - ').replace('_', ' ').title()
179
179
  skill_description = function.description or f"Execute {function_name}"
180
180
 
181
181
  skill = AgentSkill(
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nvidia-nat-a2a
3
- Version: 1.4.0a20251224
3
+ Version: 1.4.0rc2
4
4
  Summary: Subpackage for A2A Protocol integration in NeMo Agent Toolkit
5
5
  Author: NVIDIA Corporation
6
6
  Maintainer: NVIDIA Corporation
@@ -15,12 +15,12 @@ Classifier: Programming Language :: Python :: 3.13
15
15
  Requires-Python: <3.14,>=3.11
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE.md
18
- Requires-Dist: nvidia-nat==v1.4.0a20251224
18
+ Requires-Dist: nvidia-nat==v1.4.0-rc2
19
19
  Requires-Dist: a2a-sdk~=0.3.20
20
20
  Dynamic: license-file
21
21
 
22
22
  <!--
23
- SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
23
+ SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
24
24
  SPDX-License-Identifier: Apache-2.0
25
25
 
26
26
  Licensed under the Apache License, Version 2.0 (the "License");
@@ -0,0 +1,24 @@
1
+ nat/meta/pypi.md,sha256=dbcW3Z1lelHY5WjLQpB8uNv8dY2of31hQ0EV8KoOiNI,1666
2
+ nat/plugins/a2a/__init__.py,sha256=hiEJ2ajB5zhZuZOUHAyHfTA9w9UNhygij7hp4yfA6QE,685
3
+ nat/plugins/a2a/register.py,sha256=-_SHPsqmVuFNlo6fwnXpZA9whIlMKifk4vnBfNX33mA,858
4
+ nat/plugins/a2a/auth/__init__.py,sha256=9NviPz5TT6MRGgkaqDORYoEFJng0aiTUbJqlsZxNKC0,731
5
+ nat/plugins/a2a/auth/credential_service.py,sha256=xTVX68L-dW31PIseHRaVFgISDxRdJHpyIj6L_tIfoPA,15972
6
+ nat/plugins/a2a/cli/__init__.py,sha256=PGHkqZGf07j-wBfUk-D8-MocQ06oiwAmn3Dy2ttLDaQ,709
7
+ nat/plugins/a2a/cli/commands.py,sha256=3vnx1VWlLuvathrKsMBwDzRjAvvArtmOTnocKNdmttY,29536
8
+ nat/plugins/a2a/client/__init__.py,sha256=hiEJ2ajB5zhZuZOUHAyHfTA9w9UNhygij7hp4yfA6QE,685
9
+ nat/plugins/a2a/client/client_base.py,sha256=80rSNokaC90ImorDOLWMLGvntm8lzSI_W9hCuYbD4l8,13371
10
+ nat/plugins/a2a/client/client_config.py,sha256=dDsdVR9ZLf7ihmHf_g9Osh0oH7fQcbHNiipZsxqyaPo,2880
11
+ nat/plugins/a2a/client/client_impl.py,sha256=J3CghF6rUGtzMSahOOWy1L31V1hrYKdiZbmJnMqXjRU,12963
12
+ nat/plugins/a2a/server/__init__.py,sha256=hiEJ2ajB5zhZuZOUHAyHfTA9w9UNhygij7hp4yfA6QE,685
13
+ nat/plugins/a2a/server/agent_executor_adapter.py,sha256=_fqPseJXhH9PFX6rvYNOOY8jxhs1ykQuHEUyU0PN3JU,7020
14
+ nat/plugins/a2a/server/front_end_config.py,sha256=ny-H_0baW5JhhPU-WAl9fczttwRG_tco_mC3Hzk_Jl0,4907
15
+ nat/plugins/a2a/server/front_end_plugin.py,sha256=G0jBcmLH5u0Kch-7_dQSUakI9RqbMzeO5xFIuXGctSY,4901
16
+ nat/plugins/a2a/server/front_end_plugin_worker.py,sha256=ejGU8osQM2MOzrp32ZOY5XJws4nd8Uu_G79Xs1DUh1g,11702
17
+ nat/plugins/a2a/server/oauth_middleware.py,sha256=K_wvdyrZcKDJXNd_ZxqjSr6i1cZ7fPE28Yfvr3DvdIA,4756
18
+ nat/plugins/a2a/server/register_frontend.py,sha256=2vORvVf6Y0EX4ccNnYpFcZ_VOmH4qfdfoJXgWmSedy0,1485
19
+ nvidia_nat_a2a-1.4.0rc2.dist-info/licenses/LICENSE.md,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
20
+ nvidia_nat_a2a-1.4.0rc2.dist-info/METADATA,sha256=_Twqb8_SduSCvvMO6jNcO8D30tOJTrhjWW8wLAD4SUA,2432
21
+ nvidia_nat_a2a-1.4.0rc2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
+ nvidia_nat_a2a-1.4.0rc2.dist-info/entry_points.txt,sha256=pJzjTpzHY3p6xwedXlEMMm7cOKyze9pFue4PoN4ZB-I,203
23
+ nvidia_nat_a2a-1.4.0rc2.dist-info/top_level.txt,sha256=8-CJ2cP6-f0ZReXe5Hzqp-5pvzzHz-5Ds5H2bGqh1-U,4
24
+ nvidia_nat_a2a-1.4.0rc2.dist-info/RECORD,,
@@ -1,3 +1,6 @@
1
+ [nat.cli]
2
+ a2a = nat.plugins.a2a.cli.commands:a2a_command
3
+
1
4
  [nat.components]
2
5
  nat_a2a_client = nat.plugins.a2a.client.client_impl
3
6
 
@@ -1,22 +0,0 @@
1
- nat/meta/pypi.md,sha256=YkfjzZntzheoaBie5ZovnAwB78xxVqk9sblkZRZcdLU,1661
2
- nat/plugins/a2a/__init__.py,sha256=GUJrgGtpvyMUCjUBvR3faAdv-tZzbU9W-izgx9aMEQg,680
3
- nat/plugins/a2a/register.py,sha256=pUN1hbJ38M8GbdNcA0qQzJ1S-ZC91GnRGk_8SO_kTVg,853
4
- nat/plugins/a2a/auth/__init__.py,sha256=iQFx1YrjFcepS7k8jp93A0IVOkFeNx_I35M6dIngoJA,726
5
- nat/plugins/a2a/auth/credential_service.py,sha256=-_VdDF4YESaAtY1ONUiOL5z4aGDJZYVuhyhI9BZhuyI,15967
6
- nat/plugins/a2a/client/__init__.py,sha256=GUJrgGtpvyMUCjUBvR3faAdv-tZzbU9W-izgx9aMEQg,680
7
- nat/plugins/a2a/client/client_base.py,sha256=xShDZDFKa4R2XsY3yBMvM-eDaf_0cdE48XJzQ4WcEOw,13366
8
- nat/plugins/a2a/client/client_config.py,sha256=KwWjymDg9GUfSYcIaBhcxph4Hu6IeTe414hrNUUo-6g,2875
9
- nat/plugins/a2a/client/client_impl.py,sha256=CGAjiHr6EyWcnlSipmT8ixgjD4s8VbPRBPOZy2q_Sm0,12958
10
- nat/plugins/a2a/server/__init__.py,sha256=GUJrgGtpvyMUCjUBvR3faAdv-tZzbU9W-izgx9aMEQg,680
11
- nat/plugins/a2a/server/agent_executor_adapter.py,sha256=wvGXOb3FcV0_pYRv-yr-QzozjzXM909D49Dxm9199xI,7015
12
- nat/plugins/a2a/server/front_end_config.py,sha256=Lg-qjDmC4fwrwnHNtSRl54pMpdwVnO06xhgbLt-aEZY,4902
13
- nat/plugins/a2a/server/front_end_plugin.py,sha256=fX3Lagkd48snSiNo2IMTRpR-40WHUWQidpjKu8uQChY,4896
14
- nat/plugins/a2a/server/front_end_plugin_worker.py,sha256=Ehdv6lyUcrWkfMq7YomD4NYFAusrtQ2JYj2HnkIqGhY,11696
15
- nat/plugins/a2a/server/oauth_middleware.py,sha256=NvvIJSPB8wRui2eQlxr6AaNhN0JxdUQ1Ajr8Dnk0rnY,4751
16
- nat/plugins/a2a/server/register_frontend.py,sha256=4TmpBcZF4x71c2xnWuketsygqHmU7D2hKA2bzO34TpU,1480
17
- nvidia_nat_a2a-1.4.0a20251224.dist-info/licenses/LICENSE.md,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
18
- nvidia_nat_a2a-1.4.0a20251224.dist-info/METADATA,sha256=7KIbUZYZ3X7iQhe1bvuUrLvSyQndAHLAXxmVxtzrSms,2438
19
- nvidia_nat_a2a-1.4.0a20251224.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- nvidia_nat_a2a-1.4.0a20251224.dist-info/entry_points.txt,sha256=Lacvy6nXpDTv8dh8vKJ_QE8TobliVdhgABuw25t8fBg,145
21
- nvidia_nat_a2a-1.4.0a20251224.dist-info/top_level.txt,sha256=8-CJ2cP6-f0ZReXe5Hzqp-5pvzzHz-5Ds5H2bGqh1-U,4
22
- nvidia_nat_a2a-1.4.0a20251224.dist-info/RECORD,,