adcp 2.12.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.
- adcp/__init__.py +364 -0
- adcp/__main__.py +440 -0
- adcp/adagents.py +642 -0
- adcp/client.py +1057 -0
- adcp/config.py +82 -0
- adcp/exceptions.py +185 -0
- adcp/protocols/__init__.py +9 -0
- adcp/protocols/a2a.py +484 -0
- adcp/protocols/base.py +190 -0
- adcp/protocols/mcp.py +440 -0
- adcp/py.typed +0 -0
- adcp/simple.py +451 -0
- adcp/testing/__init__.py +53 -0
- adcp/testing/test_helpers.py +311 -0
- adcp/types/__init__.py +561 -0
- adcp/types/_generated.py +237 -0
- adcp/types/aliases.py +748 -0
- adcp/types/base.py +26 -0
- adcp/types/core.py +174 -0
- adcp/types/generated_poc/__init__.py +3 -0
- adcp/types/generated_poc/adagents.py +411 -0
- adcp/types/generated_poc/core/__init__.py +3 -0
- adcp/types/generated_poc/core/activation_key.py +30 -0
- adcp/types/generated_poc/core/assets/__init__.py +3 -0
- adcp/types/generated_poc/core/assets/audio_asset.py +26 -0
- adcp/types/generated_poc/core/assets/css_asset.py +20 -0
- adcp/types/generated_poc/core/assets/daast_asset.py +61 -0
- adcp/types/generated_poc/core/assets/html_asset.py +18 -0
- adcp/types/generated_poc/core/assets/image_asset.py +19 -0
- adcp/types/generated_poc/core/assets/javascript_asset.py +23 -0
- adcp/types/generated_poc/core/assets/text_asset.py +20 -0
- adcp/types/generated_poc/core/assets/url_asset.py +28 -0
- adcp/types/generated_poc/core/assets/vast_asset.py +63 -0
- adcp/types/generated_poc/core/assets/video_asset.py +24 -0
- adcp/types/generated_poc/core/assets/webhook_asset.py +53 -0
- adcp/types/generated_poc/core/brand_manifest.py +201 -0
- adcp/types/generated_poc/core/context.py +15 -0
- adcp/types/generated_poc/core/creative_asset.py +102 -0
- adcp/types/generated_poc/core/creative_assignment.py +27 -0
- adcp/types/generated_poc/core/creative_filters.py +86 -0
- adcp/types/generated_poc/core/creative_manifest.py +68 -0
- adcp/types/generated_poc/core/creative_policy.py +28 -0
- adcp/types/generated_poc/core/delivery_metrics.py +111 -0
- adcp/types/generated_poc/core/deployment.py +78 -0
- adcp/types/generated_poc/core/destination.py +43 -0
- adcp/types/generated_poc/core/dimensions.py +18 -0
- adcp/types/generated_poc/core/error.py +29 -0
- adcp/types/generated_poc/core/ext.py +15 -0
- adcp/types/generated_poc/core/format.py +260 -0
- adcp/types/generated_poc/core/format_id.py +50 -0
- adcp/types/generated_poc/core/frequency_cap.py +19 -0
- adcp/types/generated_poc/core/measurement.py +40 -0
- adcp/types/generated_poc/core/media_buy.py +40 -0
- adcp/types/generated_poc/core/package.py +68 -0
- adcp/types/generated_poc/core/performance_feedback.py +78 -0
- adcp/types/generated_poc/core/placement.py +37 -0
- adcp/types/generated_poc/core/product.py +164 -0
- adcp/types/generated_poc/core/product_filters.py +97 -0
- adcp/types/generated_poc/core/promoted_offerings.py +102 -0
- adcp/types/generated_poc/core/promoted_products.py +38 -0
- adcp/types/generated_poc/core/property.py +64 -0
- adcp/types/generated_poc/core/property_id.py +21 -0
- adcp/types/generated_poc/core/property_tag.py +21 -0
- adcp/types/generated_poc/core/protocol_envelope.py +61 -0
- adcp/types/generated_poc/core/publisher_property_selector.py +75 -0
- adcp/types/generated_poc/core/push_notification_config.py +51 -0
- adcp/types/generated_poc/core/reporting_capabilities.py +51 -0
- adcp/types/generated_poc/core/response.py +24 -0
- adcp/types/generated_poc/core/signal_filters.py +29 -0
- adcp/types/generated_poc/core/sub_asset.py +55 -0
- adcp/types/generated_poc/core/targeting.py +53 -0
- adcp/types/generated_poc/core/webhook_payload.py +96 -0
- adcp/types/generated_poc/creative/__init__.py +3 -0
- adcp/types/generated_poc/creative/list_creative_formats_request.py +88 -0
- adcp/types/generated_poc/creative/list_creative_formats_response.py +55 -0
- adcp/types/generated_poc/creative/preview_creative_request.py +153 -0
- adcp/types/generated_poc/creative/preview_creative_response.py +169 -0
- adcp/types/generated_poc/creative/preview_render.py +152 -0
- adcp/types/generated_poc/enums/__init__.py +3 -0
- adcp/types/generated_poc/enums/adcp_domain.py +12 -0
- adcp/types/generated_poc/enums/asset_content_type.py +23 -0
- adcp/types/generated_poc/enums/auth_scheme.py +12 -0
- adcp/types/generated_poc/enums/available_metric.py +19 -0
- adcp/types/generated_poc/enums/channels.py +19 -0
- adcp/types/generated_poc/enums/co_branding_requirement.py +13 -0
- adcp/types/generated_poc/enums/creative_action.py +15 -0
- adcp/types/generated_poc/enums/creative_agent_capability.py +14 -0
- adcp/types/generated_poc/enums/creative_sort_field.py +16 -0
- adcp/types/generated_poc/enums/creative_status.py +14 -0
- adcp/types/generated_poc/enums/daast_tracking_event.py +21 -0
- adcp/types/generated_poc/enums/daast_version.py +12 -0
- adcp/types/generated_poc/enums/delivery_type.py +12 -0
- adcp/types/generated_poc/enums/dimension_unit.py +14 -0
- adcp/types/generated_poc/enums/feed_format.py +13 -0
- adcp/types/generated_poc/enums/feedback_source.py +14 -0
- adcp/types/generated_poc/enums/format_category.py +17 -0
- adcp/types/generated_poc/enums/format_id_parameter.py +12 -0
- adcp/types/generated_poc/enums/frequency_cap_scope.py +16 -0
- adcp/types/generated_poc/enums/history_entry_type.py +12 -0
- adcp/types/generated_poc/enums/http_method.py +12 -0
- adcp/types/generated_poc/enums/identifier_types.py +29 -0
- adcp/types/generated_poc/enums/javascript_module_type.py +13 -0
- adcp/types/generated_poc/enums/landing_page_requirement.py +13 -0
- adcp/types/generated_poc/enums/markdown_flavor.py +12 -0
- adcp/types/generated_poc/enums/media_buy_status.py +14 -0
- adcp/types/generated_poc/enums/metric_type.py +18 -0
- adcp/types/generated_poc/enums/notification_type.py +14 -0
- adcp/types/generated_poc/enums/pacing.py +13 -0
- adcp/types/generated_poc/enums/preview_output_format.py +12 -0
- adcp/types/generated_poc/enums/pricing_model.py +17 -0
- adcp/types/generated_poc/enums/property_type.py +17 -0
- adcp/types/generated_poc/enums/publisher_identifier_types.py +15 -0
- adcp/types/generated_poc/enums/reporting_frequency.py +13 -0
- adcp/types/generated_poc/enums/signal_catalog_type.py +13 -0
- adcp/types/generated_poc/enums/sort_direction.py +12 -0
- adcp/types/generated_poc/enums/standard_format_ids.py +45 -0
- adcp/types/generated_poc/enums/task_status.py +19 -0
- adcp/types/generated_poc/enums/task_type.py +15 -0
- adcp/types/generated_poc/enums/update_frequency.py +14 -0
- adcp/types/generated_poc/enums/url_asset_type.py +13 -0
- adcp/types/generated_poc/enums/validation_mode.py +12 -0
- adcp/types/generated_poc/enums/vast_tracking_event.py +26 -0
- adcp/types/generated_poc/enums/vast_version.py +15 -0
- adcp/types/generated_poc/enums/webhook_response_type.py +14 -0
- adcp/types/generated_poc/enums/webhook_security_method.py +13 -0
- adcp/types/generated_poc/media_buy/__init__.py +3 -0
- adcp/types/generated_poc/media_buy/build_creative_request.py +41 -0
- adcp/types/generated_poc/media_buy/build_creative_response.py +51 -0
- adcp/types/generated_poc/media_buy/create_media_buy_request.py +94 -0
- adcp/types/generated_poc/media_buy/create_media_buy_response.py +56 -0
- adcp/types/generated_poc/media_buy/get_media_buy_delivery_request.py +47 -0
- adcp/types/generated_poc/media_buy/get_media_buy_delivery_response.py +235 -0
- adcp/types/generated_poc/media_buy/get_products_request.py +48 -0
- adcp/types/generated_poc/media_buy/get_products_response.py +28 -0
- adcp/types/generated_poc/media_buy/list_authorized_properties_request.py +38 -0
- adcp/types/generated_poc/media_buy/list_authorized_properties_response.py +84 -0
- adcp/types/generated_poc/media_buy/list_creative_formats_request.py +74 -0
- adcp/types/generated_poc/media_buy/list_creative_formats_response.py +56 -0
- adcp/types/generated_poc/media_buy/list_creatives_request.py +76 -0
- adcp/types/generated_poc/media_buy/list_creatives_response.py +214 -0
- adcp/types/generated_poc/media_buy/package_request.py +63 -0
- adcp/types/generated_poc/media_buy/provide_performance_feedback_request.py +125 -0
- adcp/types/generated_poc/media_buy/provide_performance_feedback_response.py +53 -0
- adcp/types/generated_poc/media_buy/sync_creatives_request.py +63 -0
- adcp/types/generated_poc/media_buy/sync_creatives_response.py +105 -0
- adcp/types/generated_poc/media_buy/update_media_buy_request.py +195 -0
- adcp/types/generated_poc/media_buy/update_media_buy_response.py +55 -0
- adcp/types/generated_poc/pricing_options/__init__.py +3 -0
- adcp/types/generated_poc/pricing_options/cpc_option.py +43 -0
- adcp/types/generated_poc/pricing_options/cpcv_option.py +45 -0
- adcp/types/generated_poc/pricing_options/cpm_auction_option.py +58 -0
- adcp/types/generated_poc/pricing_options/cpm_fixed_option.py +43 -0
- adcp/types/generated_poc/pricing_options/cpp_option.py +64 -0
- adcp/types/generated_poc/pricing_options/cpv_option.py +77 -0
- adcp/types/generated_poc/pricing_options/flat_rate_option.py +93 -0
- adcp/types/generated_poc/pricing_options/vcpm_auction_option.py +61 -0
- adcp/types/generated_poc/pricing_options/vcpm_fixed_option.py +47 -0
- adcp/types/generated_poc/protocols/__init__.py +3 -0
- adcp/types/generated_poc/protocols/adcp_extension.py +37 -0
- adcp/types/generated_poc/signals/__init__.py +3 -0
- adcp/types/generated_poc/signals/activate_signal_request.py +32 -0
- adcp/types/generated_poc/signals/activate_signal_response.py +51 -0
- adcp/types/generated_poc/signals/get_signals_request.py +53 -0
- adcp/types/generated_poc/signals/get_signals_response.py +59 -0
- adcp/utils/__init__.py +7 -0
- adcp/utils/operation_id.py +15 -0
- adcp/utils/preview_cache.py +491 -0
- adcp/utils/response_parser.py +171 -0
- adcp/validation.py +172 -0
- adcp-2.12.0.data/data/ADCP_VERSION +1 -0
- adcp-2.12.0.dist-info/METADATA +992 -0
- adcp-2.12.0.dist-info/RECORD +176 -0
- adcp-2.12.0.dist-info/WHEEL +5 -0
- adcp-2.12.0.dist-info/entry_points.txt +2 -0
- adcp-2.12.0.dist-info/licenses/LICENSE +17 -0
- adcp-2.12.0.dist-info/top_level.txt +1 -0
adcp/__main__.py
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
"""Command-line interface for AdCP client - compatible with npx @adcp/client."""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, cast
|
|
12
|
+
|
|
13
|
+
from adcp.client import ADCPClient
|
|
14
|
+
from adcp.config import (
|
|
15
|
+
CONFIG_FILE,
|
|
16
|
+
get_agent,
|
|
17
|
+
list_agents,
|
|
18
|
+
remove_agent,
|
|
19
|
+
save_agent,
|
|
20
|
+
)
|
|
21
|
+
from adcp.types.core import AgentConfig, Protocol
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def print_json(data: Any) -> None:
|
|
25
|
+
"""Print data as JSON."""
|
|
26
|
+
from pydantic import BaseModel
|
|
27
|
+
|
|
28
|
+
# Handle Pydantic models
|
|
29
|
+
if isinstance(data, BaseModel):
|
|
30
|
+
print(data.model_dump_json(indent=2, exclude_none=True))
|
|
31
|
+
else:
|
|
32
|
+
print(json.dumps(data, indent=2, default=str))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def print_result(result: Any, json_output: bool = False) -> None:
|
|
36
|
+
"""Print result in formatted or JSON mode."""
|
|
37
|
+
if json_output:
|
|
38
|
+
# Match JavaScript client: output just the data for scripting
|
|
39
|
+
if result.success and result.data:
|
|
40
|
+
print_json(result.data)
|
|
41
|
+
else:
|
|
42
|
+
# On error, output error info
|
|
43
|
+
print_json({"error": result.error, "success": False})
|
|
44
|
+
else:
|
|
45
|
+
# Pretty output with message and data (like JavaScript client)
|
|
46
|
+
if result.success:
|
|
47
|
+
print("\nSUCCESS\n")
|
|
48
|
+
# Show protocol message if available
|
|
49
|
+
if hasattr(result, "message") and result.message:
|
|
50
|
+
print("Protocol Message:")
|
|
51
|
+
print(result.message)
|
|
52
|
+
print()
|
|
53
|
+
if result.data:
|
|
54
|
+
print("Response:")
|
|
55
|
+
print_json(result.data)
|
|
56
|
+
else:
|
|
57
|
+
print("\nFAILED\n")
|
|
58
|
+
print(f"Error: {result.error}")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def execute_tool(
|
|
62
|
+
agent_config: dict[str, Any], tool_name: str, payload: dict[str, Any], json_output: bool = False
|
|
63
|
+
) -> None:
|
|
64
|
+
"""Execute a tool on an agent."""
|
|
65
|
+
# Ensure required fields
|
|
66
|
+
if "id" not in agent_config:
|
|
67
|
+
agent_config["id"] = agent_config.get("agent_uri", "unknown")
|
|
68
|
+
|
|
69
|
+
if "protocol" not in agent_config:
|
|
70
|
+
agent_config["protocol"] = "mcp"
|
|
71
|
+
|
|
72
|
+
# Convert string protocol to enum
|
|
73
|
+
if isinstance(agent_config["protocol"], str):
|
|
74
|
+
agent_config["protocol"] = Protocol(agent_config["protocol"].lower())
|
|
75
|
+
|
|
76
|
+
config = AgentConfig(**agent_config)
|
|
77
|
+
|
|
78
|
+
async with ADCPClient(config) as client:
|
|
79
|
+
# Dispatch to specific method based on tool name
|
|
80
|
+
result = await _dispatch_tool(client, tool_name, payload)
|
|
81
|
+
print_result(result, json_output)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Tool dispatch mapping - single source of truth for ADCP methods
|
|
85
|
+
# Types are filled at runtime to avoid circular imports
|
|
86
|
+
# Special case: list_tools and get_info take no parameters (None means no request type)
|
|
87
|
+
TOOL_DISPATCH: dict[str, tuple[str, type | None]] = {
|
|
88
|
+
"list_tools": ("list_tools", None), # Protocol introspection - no request type
|
|
89
|
+
"get_info": ("get_info", None), # Agent info - no request type
|
|
90
|
+
"get_products": ("get_products", None),
|
|
91
|
+
"list_creative_formats": ("list_creative_formats", None),
|
|
92
|
+
"preview_creative": ("preview_creative", None),
|
|
93
|
+
"build_creative": ("build_creative", None),
|
|
94
|
+
"sync_creatives": ("sync_creatives", None),
|
|
95
|
+
"list_creatives": ("list_creatives", None),
|
|
96
|
+
"create_media_buy": ("create_media_buy", None),
|
|
97
|
+
"update_media_buy": ("update_media_buy", None),
|
|
98
|
+
"get_media_buy_delivery": ("get_media_buy_delivery", None),
|
|
99
|
+
"list_authorized_properties": ("list_authorized_properties", None),
|
|
100
|
+
"get_signals": ("get_signals", None),
|
|
101
|
+
"activate_signal": ("activate_signal", None),
|
|
102
|
+
"provide_performance_feedback": ("provide_performance_feedback", None),
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async def _dispatch_tool(client: ADCPClient, tool_name: str, payload: dict[str, Any]) -> Any:
|
|
107
|
+
"""Dispatch tool call to appropriate client method.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
client: ADCP client instance
|
|
111
|
+
tool_name: Name of the tool to invoke
|
|
112
|
+
payload: Request payload as dict
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
TaskResult with typed response or error
|
|
116
|
+
|
|
117
|
+
Raises:
|
|
118
|
+
ValidationError: If payload doesn't match request schema (caught and returned as TaskResult)
|
|
119
|
+
"""
|
|
120
|
+
from pydantic import ValidationError
|
|
121
|
+
|
|
122
|
+
from adcp.types import _generated as gen
|
|
123
|
+
from adcp.types.core import TaskResult, TaskStatus
|
|
124
|
+
|
|
125
|
+
# Lazy initialization of request types (avoid circular imports)
|
|
126
|
+
if TOOL_DISPATCH["get_products"][1] is None:
|
|
127
|
+
TOOL_DISPATCH["get_products"] = ("get_products", gen.GetProductsRequest)
|
|
128
|
+
TOOL_DISPATCH["list_creative_formats"] = (
|
|
129
|
+
"list_creative_formats",
|
|
130
|
+
gen.ListCreativeFormatsRequest,
|
|
131
|
+
)
|
|
132
|
+
TOOL_DISPATCH["preview_creative"] = ("preview_creative", gen.PreviewCreativeRequest)
|
|
133
|
+
TOOL_DISPATCH["build_creative"] = ("build_creative", gen.BuildCreativeRequest)
|
|
134
|
+
TOOL_DISPATCH["sync_creatives"] = ("sync_creatives", gen.SyncCreativesRequest)
|
|
135
|
+
TOOL_DISPATCH["list_creatives"] = ("list_creatives", gen.ListCreativesRequest)
|
|
136
|
+
TOOL_DISPATCH["create_media_buy"] = ("create_media_buy", gen.CreateMediaBuyRequest)
|
|
137
|
+
TOOL_DISPATCH["update_media_buy"] = ("update_media_buy", gen.UpdateMediaBuyRequest)
|
|
138
|
+
TOOL_DISPATCH["get_media_buy_delivery"] = (
|
|
139
|
+
"get_media_buy_delivery",
|
|
140
|
+
gen.GetMediaBuyDeliveryRequest,
|
|
141
|
+
)
|
|
142
|
+
TOOL_DISPATCH["list_authorized_properties"] = (
|
|
143
|
+
"list_authorized_properties",
|
|
144
|
+
gen.ListAuthorizedPropertiesRequest,
|
|
145
|
+
)
|
|
146
|
+
TOOL_DISPATCH["get_signals"] = ("get_signals", gen.GetSignalsRequest)
|
|
147
|
+
TOOL_DISPATCH["activate_signal"] = ("activate_signal", gen.ActivateSignalRequest)
|
|
148
|
+
TOOL_DISPATCH["provide_performance_feedback"] = (
|
|
149
|
+
"provide_performance_feedback",
|
|
150
|
+
gen.ProvidePerformanceFeedbackRequest,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Check if tool exists
|
|
154
|
+
if tool_name not in TOOL_DISPATCH:
|
|
155
|
+
available = ", ".join(sorted(TOOL_DISPATCH.keys()))
|
|
156
|
+
return TaskResult(
|
|
157
|
+
status=TaskStatus.FAILED,
|
|
158
|
+
success=False,
|
|
159
|
+
error=f"Unknown tool: {tool_name}. Available tools: {available}",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Get method and request type
|
|
163
|
+
method_name, request_type = TOOL_DISPATCH[tool_name]
|
|
164
|
+
method = getattr(client, method_name)
|
|
165
|
+
|
|
166
|
+
# Special case: list_tools and get_info take no parameters and return
|
|
167
|
+
# data directly, not TaskResult
|
|
168
|
+
if tool_name == "list_tools":
|
|
169
|
+
try:
|
|
170
|
+
tools = await method()
|
|
171
|
+
return TaskResult(
|
|
172
|
+
status=TaskStatus.COMPLETED,
|
|
173
|
+
data={"tools": tools},
|
|
174
|
+
success=True,
|
|
175
|
+
)
|
|
176
|
+
except Exception as e:
|
|
177
|
+
return TaskResult(
|
|
178
|
+
status=TaskStatus.FAILED,
|
|
179
|
+
success=False,
|
|
180
|
+
error=f"Failed to list tools: {e}",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if tool_name == "get_info":
|
|
184
|
+
try:
|
|
185
|
+
info = await method()
|
|
186
|
+
return TaskResult(
|
|
187
|
+
status=TaskStatus.COMPLETED,
|
|
188
|
+
data=info,
|
|
189
|
+
success=True,
|
|
190
|
+
)
|
|
191
|
+
except Exception as e:
|
|
192
|
+
return TaskResult(
|
|
193
|
+
status=TaskStatus.FAILED,
|
|
194
|
+
success=False,
|
|
195
|
+
error=f"Failed to get agent info: {e}",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Type guard - request_type should be initialized by this point for methods that need it
|
|
199
|
+
if request_type is None:
|
|
200
|
+
return TaskResult(
|
|
201
|
+
status=TaskStatus.FAILED,
|
|
202
|
+
success=False,
|
|
203
|
+
error=f"Internal error: {tool_name} request type not initialized",
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Validate and invoke
|
|
207
|
+
try:
|
|
208
|
+
request = request_type(**payload)
|
|
209
|
+
return await method(request)
|
|
210
|
+
except ValidationError as e:
|
|
211
|
+
# User-friendly error for invalid payloads
|
|
212
|
+
error_details = []
|
|
213
|
+
for error in e.errors():
|
|
214
|
+
field = ".".join(str(loc) for loc in error["loc"])
|
|
215
|
+
msg = error["msg"]
|
|
216
|
+
error_details.append(f" - {field}: {msg}")
|
|
217
|
+
|
|
218
|
+
return TaskResult(
|
|
219
|
+
status=TaskStatus.FAILED,
|
|
220
|
+
success=False,
|
|
221
|
+
error=f"Invalid request payload for {tool_name}:\n" + "\n".join(error_details),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def load_payload(payload_arg: str | None) -> dict[str, Any]:
|
|
226
|
+
"""Load payload from argument (JSON, @file, or stdin)."""
|
|
227
|
+
if not payload_arg:
|
|
228
|
+
# Try to read from stdin if available and has data
|
|
229
|
+
if not sys.stdin.isatty():
|
|
230
|
+
try:
|
|
231
|
+
return cast(dict[str, Any], json.load(sys.stdin))
|
|
232
|
+
except (json.JSONDecodeError, ValueError):
|
|
233
|
+
pass
|
|
234
|
+
return {}
|
|
235
|
+
|
|
236
|
+
if payload_arg.startswith("@"):
|
|
237
|
+
# Load from file
|
|
238
|
+
file_path = Path(payload_arg[1:])
|
|
239
|
+
if not file_path.exists():
|
|
240
|
+
print(f"Error: File not found: {file_path}", file=sys.stderr)
|
|
241
|
+
sys.exit(1)
|
|
242
|
+
return cast(dict[str, Any], json.loads(file_path.read_text()))
|
|
243
|
+
|
|
244
|
+
# Parse as JSON
|
|
245
|
+
try:
|
|
246
|
+
return cast(dict[str, Any], json.loads(payload_arg))
|
|
247
|
+
except json.JSONDecodeError as e:
|
|
248
|
+
print(f"Error: Invalid JSON payload: {e}", file=sys.stderr)
|
|
249
|
+
sys.exit(1)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def handle_save_auth(alias: str, url: str | None, protocol: str | None) -> None:
|
|
253
|
+
"""Handle --save-auth command."""
|
|
254
|
+
if not url:
|
|
255
|
+
# Interactive mode
|
|
256
|
+
url = input(f"Agent URL for '{alias}': ").strip()
|
|
257
|
+
if not url:
|
|
258
|
+
print("Error: URL is required", file=sys.stderr)
|
|
259
|
+
sys.exit(1)
|
|
260
|
+
|
|
261
|
+
if not protocol:
|
|
262
|
+
protocol = input("Protocol (mcp/a2a) [mcp]: ").strip() or "mcp"
|
|
263
|
+
|
|
264
|
+
auth_token = input("Auth token (optional): ").strip() or None
|
|
265
|
+
|
|
266
|
+
save_agent(alias, url, protocol, auth_token)
|
|
267
|
+
print(f"✓ Saved agent '{alias}'")
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def handle_list_agents() -> None:
|
|
271
|
+
"""Handle --list-agents command."""
|
|
272
|
+
agents = list_agents()
|
|
273
|
+
|
|
274
|
+
if not agents:
|
|
275
|
+
print("No saved agents")
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
print("\nSaved agents:")
|
|
279
|
+
for alias, config in agents.items():
|
|
280
|
+
auth = "yes" if config.get("auth_token") else "no"
|
|
281
|
+
print(f" {alias}")
|
|
282
|
+
print(f" URL: {config.get('agent_uri')}")
|
|
283
|
+
print(f" Protocol: {config.get('protocol', 'mcp').upper()}")
|
|
284
|
+
print(f" Auth: {auth}")
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def handle_remove_agent(alias: str) -> None:
|
|
288
|
+
"""Handle --remove-agent command."""
|
|
289
|
+
if remove_agent(alias):
|
|
290
|
+
print(f"✓ Removed agent '{alias}'")
|
|
291
|
+
else:
|
|
292
|
+
print(f"Error: Agent '{alias}' not found", file=sys.stderr)
|
|
293
|
+
sys.exit(1)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def handle_show_config() -> None:
|
|
297
|
+
"""Handle --show-config command."""
|
|
298
|
+
print(f"Config file: {CONFIG_FILE}")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def resolve_agent_config(agent_identifier: str) -> dict[str, Any]:
|
|
302
|
+
"""Resolve agent identifier to configuration."""
|
|
303
|
+
# Check if it's a saved alias
|
|
304
|
+
saved = get_agent(agent_identifier)
|
|
305
|
+
if saved:
|
|
306
|
+
return saved
|
|
307
|
+
|
|
308
|
+
# Check if it's a URL
|
|
309
|
+
if agent_identifier.startswith(("http://", "https://")):
|
|
310
|
+
return {
|
|
311
|
+
"id": agent_identifier.split("/")[-1],
|
|
312
|
+
"agent_uri": agent_identifier,
|
|
313
|
+
"protocol": "mcp",
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
# Check if it's a JSON config
|
|
317
|
+
if agent_identifier.startswith("{"):
|
|
318
|
+
try:
|
|
319
|
+
return cast(dict[str, Any], json.loads(agent_identifier))
|
|
320
|
+
except json.JSONDecodeError:
|
|
321
|
+
pass
|
|
322
|
+
|
|
323
|
+
print(f"Error: Unknown agent '{agent_identifier}'", file=sys.stderr)
|
|
324
|
+
print(" Not found as saved alias", file=sys.stderr)
|
|
325
|
+
print(" Not a valid URL", file=sys.stderr)
|
|
326
|
+
print(" Not valid JSON config", file=sys.stderr)
|
|
327
|
+
sys.exit(1)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def main() -> None:
|
|
331
|
+
"""Main CLI entry point - compatible with JavaScript version."""
|
|
332
|
+
parser = argparse.ArgumentParser(
|
|
333
|
+
description="AdCP Client - Interact with AdCP agents",
|
|
334
|
+
usage="adcp [options] <agent> [tool] [payload]",
|
|
335
|
+
add_help=False,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# Configuration management
|
|
339
|
+
parser.add_argument("--save-auth", metavar="ALIAS", help="Save agent configuration")
|
|
340
|
+
parser.add_argument("--list-agents", action="store_true", help="List saved agents")
|
|
341
|
+
parser.add_argument("--remove-agent", metavar="ALIAS", help="Remove saved agent")
|
|
342
|
+
parser.add_argument("--show-config", action="store_true", help="Show config file location")
|
|
343
|
+
parser.add_argument("--version", action="store_true", help="Show SDK and AdCP version")
|
|
344
|
+
|
|
345
|
+
# Execution options
|
|
346
|
+
parser.add_argument("--protocol", choices=["mcp", "a2a"], help="Force protocol type")
|
|
347
|
+
parser.add_argument("--auth", help="Authentication token")
|
|
348
|
+
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
349
|
+
parser.add_argument("--debug", action="store_true", help="Enable debug mode")
|
|
350
|
+
parser.add_argument("--help", "-h", action="store_true", help="Show help")
|
|
351
|
+
|
|
352
|
+
# Positional arguments
|
|
353
|
+
parser.add_argument("agent", nargs="?", help="Agent alias, URL, or config")
|
|
354
|
+
parser.add_argument("tool", nargs="?", help="Tool name to execute")
|
|
355
|
+
parser.add_argument("payload", nargs="?", help="Payload (JSON, @file, or stdin)")
|
|
356
|
+
|
|
357
|
+
# Parse known args to handle --save-auth with positional args
|
|
358
|
+
args, remaining = parser.parse_known_args()
|
|
359
|
+
|
|
360
|
+
# Handle help
|
|
361
|
+
if args.help or (
|
|
362
|
+
not args.agent
|
|
363
|
+
and not any(
|
|
364
|
+
[
|
|
365
|
+
args.save_auth,
|
|
366
|
+
args.list_agents,
|
|
367
|
+
args.remove_agent,
|
|
368
|
+
args.show_config,
|
|
369
|
+
args.version,
|
|
370
|
+
]
|
|
371
|
+
)
|
|
372
|
+
):
|
|
373
|
+
parser.print_help()
|
|
374
|
+
print("\nExamples:")
|
|
375
|
+
print(" adcp --version")
|
|
376
|
+
print(" adcp --save-auth myagent https://agent.example.com mcp")
|
|
377
|
+
print(" adcp --list-agents")
|
|
378
|
+
print(" adcp myagent get_info")
|
|
379
|
+
print(" adcp myagent list_tools")
|
|
380
|
+
print(' adcp myagent get_products \'{"brief":"TV ads"}\'')
|
|
381
|
+
print(" adcp https://agent.example.com list_tools")
|
|
382
|
+
sys.exit(0)
|
|
383
|
+
|
|
384
|
+
# Handle configuration commands
|
|
385
|
+
if args.version:
|
|
386
|
+
from adcp import __version__, get_adcp_version
|
|
387
|
+
|
|
388
|
+
print(f"AdCP Python SDK: v{__version__}")
|
|
389
|
+
print(f"Target AdCP Spec: {get_adcp_version()}")
|
|
390
|
+
sys.exit(0)
|
|
391
|
+
|
|
392
|
+
if args.save_auth:
|
|
393
|
+
url = args.agent if args.agent else None
|
|
394
|
+
protocol = args.tool if args.tool else None
|
|
395
|
+
handle_save_auth(args.save_auth, url, protocol)
|
|
396
|
+
sys.exit(0)
|
|
397
|
+
|
|
398
|
+
if args.list_agents:
|
|
399
|
+
handle_list_agents()
|
|
400
|
+
sys.exit(0)
|
|
401
|
+
|
|
402
|
+
if args.remove_agent:
|
|
403
|
+
handle_remove_agent(args.remove_agent)
|
|
404
|
+
sys.exit(0)
|
|
405
|
+
|
|
406
|
+
if args.show_config:
|
|
407
|
+
handle_show_config()
|
|
408
|
+
sys.exit(0)
|
|
409
|
+
|
|
410
|
+
# Execute tool
|
|
411
|
+
if not args.agent:
|
|
412
|
+
print("Error: Agent identifier required", file=sys.stderr)
|
|
413
|
+
sys.exit(1)
|
|
414
|
+
|
|
415
|
+
if not args.tool:
|
|
416
|
+
print("Error: Tool name required", file=sys.stderr)
|
|
417
|
+
sys.exit(1)
|
|
418
|
+
|
|
419
|
+
# Resolve agent config
|
|
420
|
+
agent_config = resolve_agent_config(args.agent)
|
|
421
|
+
|
|
422
|
+
# Override with command-line options
|
|
423
|
+
if args.protocol:
|
|
424
|
+
agent_config["protocol"] = args.protocol
|
|
425
|
+
|
|
426
|
+
if args.auth:
|
|
427
|
+
agent_config["auth_token"] = args.auth
|
|
428
|
+
|
|
429
|
+
if args.debug:
|
|
430
|
+
agent_config["debug"] = True
|
|
431
|
+
|
|
432
|
+
# Load payload
|
|
433
|
+
payload = load_payload(args.payload)
|
|
434
|
+
|
|
435
|
+
# Execute
|
|
436
|
+
asyncio.run(execute_tool(agent_config, args.tool, payload, args.json))
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
if __name__ == "__main__":
|
|
440
|
+
main()
|