onetool-mcp 1.0.0b1__py3-none-any.whl → 1.0.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.
- onetool/cli.py +63 -4
- onetool_mcp-1.0.0rc2.dist-info/METADATA +266 -0
- onetool_mcp-1.0.0rc2.dist-info/RECORD +129 -0
- {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/licenses/LICENSE.txt +1 -1
- {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/licenses/NOTICE.txt +54 -64
- ot/__main__.py +6 -6
- ot/config/__init__.py +48 -46
- ot/config/global_templates/__init__.py +2 -2
- ot/config/{defaults → global_templates}/diagram-templates/api-flow.mmd +33 -33
- ot/config/{defaults → global_templates}/diagram-templates/c4-context.puml +30 -30
- ot/config/{defaults → global_templates}/diagram-templates/class-diagram.mmd +87 -87
- ot/config/{defaults → global_templates}/diagram-templates/feature-mindmap.mmd +70 -70
- ot/config/{defaults → global_templates}/diagram-templates/microservices.d2 +81 -81
- ot/config/{defaults → global_templates}/diagram-templates/project-gantt.mmd +37 -37
- ot/config/{defaults → global_templates}/diagram-templates/state-machine.mmd +42 -42
- ot/config/global_templates/diagram.yaml +167 -0
- ot/config/global_templates/onetool.yaml +3 -1
- ot/config/{defaults → global_templates}/prompts.yaml +102 -97
- ot/config/global_templates/security.yaml +31 -0
- ot/config/global_templates/servers.yaml +93 -12
- ot/config/global_templates/snippets.yaml +5 -26
- ot/config/{defaults → global_templates}/tool_templates/__init__.py +7 -7
- ot/config/loader.py +221 -105
- ot/config/mcp.py +5 -1
- ot/config/secrets.py +192 -190
- ot/decorators.py +116 -116
- ot/executor/__init__.py +35 -35
- ot/executor/base.py +16 -16
- ot/executor/fence_processor.py +83 -83
- ot/executor/linter.py +142 -142
- ot/executor/pep723.py +288 -288
- ot/executor/runner.py +20 -6
- ot/executor/simple.py +163 -163
- ot/executor/validator.py +603 -164
- ot/http_client.py +145 -145
- ot/logging/__init__.py +37 -37
- ot/logging/entry.py +213 -213
- ot/logging/format.py +191 -188
- ot/logging/span.py +349 -349
- ot/meta.py +236 -14
- ot/paths.py +32 -49
- ot/prompts.py +218 -218
- ot/proxy/manager.py +14 -2
- ot/registry/__init__.py +189 -189
- ot/registry/parser.py +269 -269
- ot/server.py +330 -315
- ot/shortcuts/__init__.py +15 -15
- ot/shortcuts/aliases.py +87 -87
- ot/shortcuts/snippets.py +258 -258
- ot/stats/__init__.py +35 -35
- ot/stats/html.py +2 -2
- ot/stats/reader.py +354 -354
- ot/stats/timing.py +57 -57
- ot/support.py +63 -63
- ot/tools.py +1 -1
- ot/utils/batch.py +161 -161
- ot/utils/cache.py +120 -120
- ot/utils/exceptions.py +23 -23
- ot/utils/factory.py +178 -179
- ot/utils/format.py +65 -65
- ot/utils/http.py +202 -202
- ot/utils/platform.py +45 -45
- ot/utils/truncate.py +69 -69
- ot_tools/__init__.py +4 -4
- ot_tools/_convert/__init__.py +12 -12
- ot_tools/_convert/pdf.py +254 -254
- ot_tools/diagram.yaml +167 -167
- ot_tools/scaffold.py +2 -2
- ot_tools/transform.py +124 -19
- ot_tools/web_fetch.py +94 -43
- onetool_mcp-1.0.0b1.dist-info/METADATA +0 -163
- onetool_mcp-1.0.0b1.dist-info/RECORD +0 -132
- ot/config/defaults/bench.yaml +0 -4
- ot/config/defaults/onetool.yaml +0 -25
- ot/config/defaults/servers.yaml +0 -7
- ot/config/defaults/snippets.yaml +0 -4
- ot_tools/firecrawl.py +0 -732
- {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/WHEEL +0 -0
- {onetool_mcp-1.0.0b1.dist-info → onetool_mcp-1.0.0rc2.dist-info}/entry_points.txt +0 -0
- /ot/config/{defaults → global_templates}/tool_templates/extension.py +0 -0
- /ot/config/{defaults → global_templates}/tool_templates/isolated.py +0 -0
ot/utils/format.py
CHANGED
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
"""Result serialization utilities for MCP responses."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
from typing import Any, Literal
|
|
7
|
-
|
|
8
|
-
import yaml
|
|
9
|
-
|
|
10
|
-
__all__ = ["FormatMode", "serialize_result"]
|
|
11
|
-
|
|
12
|
-
FormatMode = Literal["json", "json_h", "yml", "yml_h", "raw"]
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def serialize_result(result: Any, fmt: FormatMode = "json") -> str:
|
|
16
|
-
"""Serialize tool result to string for MCP response.
|
|
17
|
-
|
|
18
|
-
Tools return native Python types (dict, list, str). This function
|
|
19
|
-
serializes them to a string suitable for MCP text content.
|
|
20
|
-
|
|
21
|
-
Format modes:
|
|
22
|
-
- json: Compact JSON (default, no spaces)
|
|
23
|
-
- json_h: Human-readable JSON (2-space indent)
|
|
24
|
-
- yml: YAML flow style (compact)
|
|
25
|
-
- yml_h: YAML block style (human-readable)
|
|
26
|
-
- raw: str() conversion
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
result: Tool result (dict, list, str, or other)
|
|
30
|
-
fmt: Output format mode (default: "json")
|
|
31
|
-
|
|
32
|
-
Returns:
|
|
33
|
-
String representation suitable for MCP response
|
|
34
|
-
"""
|
|
35
|
-
# Strings pass through unchanged for all formats except raw
|
|
36
|
-
if isinstance(result, str) and fmt != "raw":
|
|
37
|
-
return result
|
|
38
|
-
|
|
39
|
-
if fmt == "raw":
|
|
40
|
-
return str(result)
|
|
41
|
-
|
|
42
|
-
if fmt == "json":
|
|
43
|
-
if isinstance(result, (dict, list)):
|
|
44
|
-
return json.dumps(result, ensure_ascii=False, separators=(",", ":"))
|
|
45
|
-
return str(result)
|
|
46
|
-
|
|
47
|
-
if fmt == "json_h":
|
|
48
|
-
if isinstance(result, (dict, list)):
|
|
49
|
-
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
50
|
-
return str(result)
|
|
51
|
-
|
|
52
|
-
if fmt == "yml":
|
|
53
|
-
if isinstance(result, (dict, list)):
|
|
54
|
-
return yaml.dump(result, default_flow_style=True, allow_unicode=True, sort_keys=False).rstrip()
|
|
55
|
-
return str(result)
|
|
56
|
-
|
|
57
|
-
if fmt == "yml_h":
|
|
58
|
-
if isinstance(result, (dict, list)):
|
|
59
|
-
return yaml.dump(result, default_flow_style=False, allow_unicode=True, sort_keys=False).rstrip()
|
|
60
|
-
return str(result)
|
|
61
|
-
|
|
62
|
-
# Unknown format, fall back to compact JSON
|
|
63
|
-
if isinstance(result, (dict, list)):
|
|
64
|
-
return json.dumps(result, ensure_ascii=False, separators=(",", ":"))
|
|
65
|
-
return str(result)
|
|
1
|
+
"""Result serialization utilities for MCP responses."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
__all__ = ["FormatMode", "serialize_result"]
|
|
11
|
+
|
|
12
|
+
FormatMode = Literal["json", "json_h", "yml", "yml_h", "raw"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def serialize_result(result: Any, fmt: FormatMode = "json") -> str:
|
|
16
|
+
"""Serialize tool result to string for MCP response.
|
|
17
|
+
|
|
18
|
+
Tools return native Python types (dict, list, str). This function
|
|
19
|
+
serializes them to a string suitable for MCP text content.
|
|
20
|
+
|
|
21
|
+
Format modes:
|
|
22
|
+
- json: Compact JSON (default, no spaces)
|
|
23
|
+
- json_h: Human-readable JSON (2-space indent)
|
|
24
|
+
- yml: YAML flow style (compact)
|
|
25
|
+
- yml_h: YAML block style (human-readable)
|
|
26
|
+
- raw: str() conversion
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
result: Tool result (dict, list, str, or other)
|
|
30
|
+
fmt: Output format mode (default: "json")
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
String representation suitable for MCP response
|
|
34
|
+
"""
|
|
35
|
+
# Strings pass through unchanged for all formats except raw
|
|
36
|
+
if isinstance(result, str) and fmt != "raw":
|
|
37
|
+
return result
|
|
38
|
+
|
|
39
|
+
if fmt == "raw":
|
|
40
|
+
return str(result)
|
|
41
|
+
|
|
42
|
+
if fmt == "json":
|
|
43
|
+
if isinstance(result, (dict, list)):
|
|
44
|
+
return json.dumps(result, ensure_ascii=False, separators=(",", ":"))
|
|
45
|
+
return str(result)
|
|
46
|
+
|
|
47
|
+
if fmt == "json_h":
|
|
48
|
+
if isinstance(result, (dict, list)):
|
|
49
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
50
|
+
return str(result)
|
|
51
|
+
|
|
52
|
+
if fmt == "yml":
|
|
53
|
+
if isinstance(result, (dict, list)):
|
|
54
|
+
return yaml.dump(result, default_flow_style=True, allow_unicode=True, sort_keys=False).rstrip()
|
|
55
|
+
return str(result)
|
|
56
|
+
|
|
57
|
+
if fmt == "yml_h":
|
|
58
|
+
if isinstance(result, (dict, list)):
|
|
59
|
+
return yaml.dump(result, default_flow_style=False, allow_unicode=True, sort_keys=False).rstrip()
|
|
60
|
+
return str(result)
|
|
61
|
+
|
|
62
|
+
# Unknown format, fall back to compact JSON
|
|
63
|
+
if isinstance(result, (dict, list)):
|
|
64
|
+
return json.dumps(result, ensure_ascii=False, separators=(",", ":"))
|
|
65
|
+
return str(result)
|
ot/utils/http.py
CHANGED
|
@@ -1,202 +1,202 @@
|
|
|
1
|
-
"""HTTP request utilities for OneTool.
|
|
2
|
-
|
|
3
|
-
Provides standardized HTTP error handling and header construction
|
|
4
|
-
for API-based tools.
|
|
5
|
-
|
|
6
|
-
Example:
|
|
7
|
-
from ot.utils import safe_request, api_headers
|
|
8
|
-
|
|
9
|
-
# Build API headers with authentication
|
|
10
|
-
headers = api_headers("MY_API_KEY", header_name="Authorization", prefix="Bearer")
|
|
11
|
-
|
|
12
|
-
# Make a request with standardized error handling
|
|
13
|
-
success, result = safe_request(
|
|
14
|
-
lambda: client.get("/endpoint", headers=headers)
|
|
15
|
-
)
|
|
16
|
-
if success:
|
|
17
|
-
data = result # Parsed JSON dict
|
|
18
|
-
else:
|
|
19
|
-
error_msg = result # Error message string
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
from __future__ import annotations
|
|
23
|
-
|
|
24
|
-
from typing import TYPE_CHECKING, Any, TypeVar
|
|
25
|
-
|
|
26
|
-
from ot.config.secrets import get_secret
|
|
27
|
-
|
|
28
|
-
if TYPE_CHECKING:
|
|
29
|
-
from collections.abc import Callable
|
|
30
|
-
|
|
31
|
-
__all__ = ["api_headers", "check_api_key", "safe_request"]
|
|
32
|
-
|
|
33
|
-
T = TypeVar("T")
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def api_headers(
|
|
37
|
-
secret_name: str,
|
|
38
|
-
*,
|
|
39
|
-
header_name: str = "Authorization",
|
|
40
|
-
prefix: str = "Bearer",
|
|
41
|
-
extra: dict[str, str] | None = None,
|
|
42
|
-
) -> dict[str, str]:
|
|
43
|
-
"""Build API request headers with authentication.
|
|
44
|
-
|
|
45
|
-
Retrieves a secret and constructs the appropriate authorization header.
|
|
46
|
-
Commonly used patterns:
|
|
47
|
-
- Bearer token: prefix="Bearer" (default)
|
|
48
|
-
- API key header: header_name="X-API-Key", prefix=""
|
|
49
|
-
|
|
50
|
-
Args:
|
|
51
|
-
secret_name: Name of secret in secrets.yaml (e.g., "MY_API_KEY")
|
|
52
|
-
header_name: Header name for the auth token (default: "Authorization")
|
|
53
|
-
prefix: Prefix before the token value (default: "Bearer")
|
|
54
|
-
extra: Additional headers to include
|
|
55
|
-
|
|
56
|
-
Returns:
|
|
57
|
-
Dict of headers ready for HTTP requests
|
|
58
|
-
|
|
59
|
-
Raises:
|
|
60
|
-
ValueError: If the secret is not configured
|
|
61
|
-
|
|
62
|
-
Example:
|
|
63
|
-
# Bearer token auth
|
|
64
|
-
headers = api_headers("OPENAI_API_KEY")
|
|
65
|
-
# {"Authorization": "Bearer sk-..."}
|
|
66
|
-
|
|
67
|
-
# Custom API key header
|
|
68
|
-
headers = api_headers(
|
|
69
|
-
"BRAVE_API_KEY", header_name="X-Subscription-Token", prefix=""
|
|
70
|
-
)
|
|
71
|
-
# {"X-Subscription-Token": "BSA..."}
|
|
72
|
-
|
|
73
|
-
# With extra headers
|
|
74
|
-
headers = api_headers("API_KEY", extra={"Accept": "application/json"})
|
|
75
|
-
"""
|
|
76
|
-
api_key = get_secret(secret_name)
|
|
77
|
-
if not api_key:
|
|
78
|
-
raise ValueError(f"{secret_name} secret not configured in secrets.yaml")
|
|
79
|
-
|
|
80
|
-
headers: dict[str, str] = {}
|
|
81
|
-
|
|
82
|
-
# Build auth header
|
|
83
|
-
if prefix:
|
|
84
|
-
headers[header_name] = f"{prefix} {api_key}"
|
|
85
|
-
else:
|
|
86
|
-
headers[header_name] = api_key
|
|
87
|
-
|
|
88
|
-
# Add extra headers
|
|
89
|
-
if extra:
|
|
90
|
-
headers.update(extra)
|
|
91
|
-
|
|
92
|
-
return headers
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def safe_request(
|
|
96
|
-
request_fn: Callable[[], T],
|
|
97
|
-
*,
|
|
98
|
-
parse_json: bool = True,
|
|
99
|
-
) -> tuple[bool, T | dict[str, Any] | str]:
|
|
100
|
-
"""Execute an HTTP request with standardized error handling.
|
|
101
|
-
|
|
102
|
-
Wraps an HTTP request call and handles common error patterns:
|
|
103
|
-
- Connection errors
|
|
104
|
-
- HTTP status errors
|
|
105
|
-
- JSON parsing errors
|
|
106
|
-
- Timeout errors
|
|
107
|
-
|
|
108
|
-
Args:
|
|
109
|
-
request_fn: Callable that makes the HTTP request and returns response
|
|
110
|
-
parse_json: If True (default), parse response as JSON
|
|
111
|
-
|
|
112
|
-
Returns:
|
|
113
|
-
Tuple of (success: bool, result).
|
|
114
|
-
On success: (True, parsed_json_dict or response)
|
|
115
|
-
On failure: (False, error_message_string)
|
|
116
|
-
|
|
117
|
-
Example:
|
|
118
|
-
# With httpx client
|
|
119
|
-
success, result = safe_request(
|
|
120
|
-
lambda: client.get("/api/data", params={"q": "test"})
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
if success:
|
|
124
|
-
data = result["data"]
|
|
125
|
-
else:
|
|
126
|
-
return f"Error: {result}"
|
|
127
|
-
|
|
128
|
-
# Without JSON parsing
|
|
129
|
-
success, result = safe_request(
|
|
130
|
-
lambda: client.get("/raw/text"),
|
|
131
|
-
parse_json=False,
|
|
132
|
-
)
|
|
133
|
-
"""
|
|
134
|
-
try:
|
|
135
|
-
response = request_fn()
|
|
136
|
-
|
|
137
|
-
# Check for raise_for_status method (httpx/requests response)
|
|
138
|
-
if hasattr(response, "raise_for_status"):
|
|
139
|
-
response.raise_for_status()
|
|
140
|
-
|
|
141
|
-
if parse_json and hasattr(response, "json"):
|
|
142
|
-
return True, response.json()
|
|
143
|
-
|
|
144
|
-
if hasattr(response, "text"):
|
|
145
|
-
return True, response.text
|
|
146
|
-
|
|
147
|
-
return True, response
|
|
148
|
-
|
|
149
|
-
except Exception as e:
|
|
150
|
-
return False, _format_http_error(e)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def _format_http_error(error: Exception) -> str:
|
|
154
|
-
"""Format an HTTP error into a user-friendly message.
|
|
155
|
-
|
|
156
|
-
Extracts status code and response text when available.
|
|
157
|
-
|
|
158
|
-
Args:
|
|
159
|
-
error: The exception from the HTTP request
|
|
160
|
-
|
|
161
|
-
Returns:
|
|
162
|
-
Formatted error message string
|
|
163
|
-
"""
|
|
164
|
-
error_type = type(error).__name__
|
|
165
|
-
|
|
166
|
-
# Check for response attribute (HTTPStatusError, etc.)
|
|
167
|
-
if hasattr(error, "response"):
|
|
168
|
-
response = error.response
|
|
169
|
-
status = getattr(response, "status_code", "unknown")
|
|
170
|
-
text = getattr(response, "text", "")[:200]
|
|
171
|
-
return f"HTTP error ({status}): {text}" if text else f"HTTP error ({status})"
|
|
172
|
-
|
|
173
|
-
# Check for common error types
|
|
174
|
-
error_str = str(error)
|
|
175
|
-
if "timeout" in error_str.lower():
|
|
176
|
-
return f"Request timeout: {error}"
|
|
177
|
-
if "connection" in error_str.lower():
|
|
178
|
-
return f"Connection error: {error}"
|
|
179
|
-
|
|
180
|
-
return f"Request failed ({error_type}): {error}"
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
def check_api_key(secret_name: str) -> str | None:
|
|
184
|
-
"""Check if an API key is configured and return error message if not.
|
|
185
|
-
|
|
186
|
-
Convenience function for early validation in tools.
|
|
187
|
-
|
|
188
|
-
Args:
|
|
189
|
-
secret_name: Name of secret to check
|
|
190
|
-
|
|
191
|
-
Returns:
|
|
192
|
-
None if configured, error message string if not
|
|
193
|
-
|
|
194
|
-
Example:
|
|
195
|
-
if error := check_api_key("BRAVE_API_KEY"):
|
|
196
|
-
return error
|
|
197
|
-
# Proceed with API calls
|
|
198
|
-
"""
|
|
199
|
-
api_key = get_secret(secret_name)
|
|
200
|
-
if not api_key:
|
|
201
|
-
return f"Error: {secret_name} secret not configured"
|
|
202
|
-
return None
|
|
1
|
+
"""HTTP request utilities for OneTool.
|
|
2
|
+
|
|
3
|
+
Provides standardized HTTP error handling and header construction
|
|
4
|
+
for API-based tools.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
from ot.utils import safe_request, api_headers
|
|
8
|
+
|
|
9
|
+
# Build API headers with authentication
|
|
10
|
+
headers = api_headers("MY_API_KEY", header_name="Authorization", prefix="Bearer")
|
|
11
|
+
|
|
12
|
+
# Make a request with standardized error handling
|
|
13
|
+
success, result = safe_request(
|
|
14
|
+
lambda: client.get("/endpoint", headers=headers)
|
|
15
|
+
)
|
|
16
|
+
if success:
|
|
17
|
+
data = result # Parsed JSON dict
|
|
18
|
+
else:
|
|
19
|
+
error_msg = result # Error message string
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
25
|
+
|
|
26
|
+
from ot.config.secrets import get_secret
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from collections.abc import Callable
|
|
30
|
+
|
|
31
|
+
__all__ = ["api_headers", "check_api_key", "safe_request"]
|
|
32
|
+
|
|
33
|
+
T = TypeVar("T")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def api_headers(
|
|
37
|
+
secret_name: str,
|
|
38
|
+
*,
|
|
39
|
+
header_name: str = "Authorization",
|
|
40
|
+
prefix: str = "Bearer",
|
|
41
|
+
extra: dict[str, str] | None = None,
|
|
42
|
+
) -> dict[str, str]:
|
|
43
|
+
"""Build API request headers with authentication.
|
|
44
|
+
|
|
45
|
+
Retrieves a secret and constructs the appropriate authorization header.
|
|
46
|
+
Commonly used patterns:
|
|
47
|
+
- Bearer token: prefix="Bearer" (default)
|
|
48
|
+
- API key header: header_name="X-API-Key", prefix=""
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
secret_name: Name of secret in secrets.yaml (e.g., "MY_API_KEY")
|
|
52
|
+
header_name: Header name for the auth token (default: "Authorization")
|
|
53
|
+
prefix: Prefix before the token value (default: "Bearer")
|
|
54
|
+
extra: Additional headers to include
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Dict of headers ready for HTTP requests
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
ValueError: If the secret is not configured
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
# Bearer token auth
|
|
64
|
+
headers = api_headers("OPENAI_API_KEY")
|
|
65
|
+
# {"Authorization": "Bearer sk-..."}
|
|
66
|
+
|
|
67
|
+
# Custom API key header
|
|
68
|
+
headers = api_headers(
|
|
69
|
+
"BRAVE_API_KEY", header_name="X-Subscription-Token", prefix=""
|
|
70
|
+
)
|
|
71
|
+
# {"X-Subscription-Token": "BSA..."}
|
|
72
|
+
|
|
73
|
+
# With extra headers
|
|
74
|
+
headers = api_headers("API_KEY", extra={"Accept": "application/json"})
|
|
75
|
+
"""
|
|
76
|
+
api_key = get_secret(secret_name)
|
|
77
|
+
if not api_key:
|
|
78
|
+
raise ValueError(f"{secret_name} secret not configured in secrets.yaml")
|
|
79
|
+
|
|
80
|
+
headers: dict[str, str] = {}
|
|
81
|
+
|
|
82
|
+
# Build auth header
|
|
83
|
+
if prefix:
|
|
84
|
+
headers[header_name] = f"{prefix} {api_key}"
|
|
85
|
+
else:
|
|
86
|
+
headers[header_name] = api_key
|
|
87
|
+
|
|
88
|
+
# Add extra headers
|
|
89
|
+
if extra:
|
|
90
|
+
headers.update(extra)
|
|
91
|
+
|
|
92
|
+
return headers
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def safe_request(
|
|
96
|
+
request_fn: Callable[[], T],
|
|
97
|
+
*,
|
|
98
|
+
parse_json: bool = True,
|
|
99
|
+
) -> tuple[bool, T | dict[str, Any] | str]:
|
|
100
|
+
"""Execute an HTTP request with standardized error handling.
|
|
101
|
+
|
|
102
|
+
Wraps an HTTP request call and handles common error patterns:
|
|
103
|
+
- Connection errors
|
|
104
|
+
- HTTP status errors
|
|
105
|
+
- JSON parsing errors
|
|
106
|
+
- Timeout errors
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
request_fn: Callable that makes the HTTP request and returns response
|
|
110
|
+
parse_json: If True (default), parse response as JSON
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Tuple of (success: bool, result).
|
|
114
|
+
On success: (True, parsed_json_dict or response)
|
|
115
|
+
On failure: (False, error_message_string)
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
# With httpx client
|
|
119
|
+
success, result = safe_request(
|
|
120
|
+
lambda: client.get("/api/data", params={"q": "test"})
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if success:
|
|
124
|
+
data = result["data"]
|
|
125
|
+
else:
|
|
126
|
+
return f"Error: {result}"
|
|
127
|
+
|
|
128
|
+
# Without JSON parsing
|
|
129
|
+
success, result = safe_request(
|
|
130
|
+
lambda: client.get("/raw/text"),
|
|
131
|
+
parse_json=False,
|
|
132
|
+
)
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
response = request_fn()
|
|
136
|
+
|
|
137
|
+
# Check for raise_for_status method (httpx/requests response)
|
|
138
|
+
if hasattr(response, "raise_for_status"):
|
|
139
|
+
response.raise_for_status()
|
|
140
|
+
|
|
141
|
+
if parse_json and hasattr(response, "json"):
|
|
142
|
+
return True, response.json()
|
|
143
|
+
|
|
144
|
+
if hasattr(response, "text"):
|
|
145
|
+
return True, response.text
|
|
146
|
+
|
|
147
|
+
return True, response
|
|
148
|
+
|
|
149
|
+
except Exception as e:
|
|
150
|
+
return False, _format_http_error(e)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _format_http_error(error: Exception) -> str:
|
|
154
|
+
"""Format an HTTP error into a user-friendly message.
|
|
155
|
+
|
|
156
|
+
Extracts status code and response text when available.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
error: The exception from the HTTP request
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Formatted error message string
|
|
163
|
+
"""
|
|
164
|
+
error_type = type(error).__name__
|
|
165
|
+
|
|
166
|
+
# Check for response attribute (HTTPStatusError, etc.)
|
|
167
|
+
if hasattr(error, "response"):
|
|
168
|
+
response = error.response
|
|
169
|
+
status = getattr(response, "status_code", "unknown")
|
|
170
|
+
text = getattr(response, "text", "")[:200]
|
|
171
|
+
return f"HTTP error ({status}): {text}" if text else f"HTTP error ({status})"
|
|
172
|
+
|
|
173
|
+
# Check for common error types
|
|
174
|
+
error_str = str(error)
|
|
175
|
+
if "timeout" in error_str.lower():
|
|
176
|
+
return f"Request timeout: {error}"
|
|
177
|
+
if "connection" in error_str.lower():
|
|
178
|
+
return f"Connection error: {error}"
|
|
179
|
+
|
|
180
|
+
return f"Request failed ({error_type}): {error}"
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def check_api_key(secret_name: str) -> str | None:
|
|
184
|
+
"""Check if an API key is configured and return error message if not.
|
|
185
|
+
|
|
186
|
+
Convenience function for early validation in tools.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
secret_name: Name of secret to check
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
None if configured, error message string if not
|
|
193
|
+
|
|
194
|
+
Example:
|
|
195
|
+
if error := check_api_key("BRAVE_API_KEY"):
|
|
196
|
+
return error
|
|
197
|
+
# Proceed with API calls
|
|
198
|
+
"""
|
|
199
|
+
api_key = get_secret(secret_name)
|
|
200
|
+
if not api_key:
|
|
201
|
+
return f"Error: {secret_name} secret not configured"
|
|
202
|
+
return None
|
ot/utils/platform.py
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
"""Platform detection utilities for cross-platform support."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import sys
|
|
6
|
-
|
|
7
|
-
# Platform-specific install commands for external dependencies
|
|
8
|
-
INSTALL_COMMANDS: dict[str, dict[str, str]] = {
|
|
9
|
-
"rg": {
|
|
10
|
-
"darwin": "brew install ripgrep",
|
|
11
|
-
"linux": "apt install ripgrep # or: snap install ripgrep",
|
|
12
|
-
"win32": "winget install BurntSushi.ripgrep.MSVC # or: scoop install ripgrep",
|
|
13
|
-
},
|
|
14
|
-
"playwright": {
|
|
15
|
-
"darwin": "pip install playwright && playwright install",
|
|
16
|
-
"linux": "pip install playwright && playwright install",
|
|
17
|
-
"win32": "pip install playwright && playwright install",
|
|
18
|
-
},
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def get_install_hint(tool: str) -> str:
|
|
23
|
-
"""Get platform-appropriate install command for a tool.
|
|
24
|
-
|
|
25
|
-
Args:
|
|
26
|
-
tool: Tool name (e.g., "rg", "playwright")
|
|
27
|
-
|
|
28
|
-
Returns:
|
|
29
|
-
Install command for the current platform, or generic message if unknown.
|
|
30
|
-
|
|
31
|
-
Example:
|
|
32
|
-
>>> get_install_hint("rg") # On macOS
|
|
33
|
-
'brew install ripgrep'
|
|
34
|
-
>>> get_install_hint("rg") # On Linux
|
|
35
|
-
'apt install ripgrep # or: snap install ripgrep'
|
|
36
|
-
>>> get_install_hint("rg") # On Windows
|
|
37
|
-
'winget install BurntSushi.ripgrep.MSVC # or: scoop install ripgrep'
|
|
38
|
-
"""
|
|
39
|
-
platform = sys.platform
|
|
40
|
-
# Normalize Linux variants (linux2, etc.)
|
|
41
|
-
if platform.startswith("linux"):
|
|
42
|
-
platform = "linux"
|
|
43
|
-
|
|
44
|
-
commands = INSTALL_COMMANDS.get(tool, {})
|
|
45
|
-
return commands.get(platform, f"Install {tool} for your platform")
|
|
1
|
+
"""Platform detection utilities for cross-platform support."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
# Platform-specific install commands for external dependencies
|
|
8
|
+
INSTALL_COMMANDS: dict[str, dict[str, str]] = {
|
|
9
|
+
"rg": {
|
|
10
|
+
"darwin": "brew install ripgrep",
|
|
11
|
+
"linux": "apt install ripgrep # or: snap install ripgrep",
|
|
12
|
+
"win32": "winget install BurntSushi.ripgrep.MSVC # or: scoop install ripgrep",
|
|
13
|
+
},
|
|
14
|
+
"playwright": {
|
|
15
|
+
"darwin": "pip install playwright && playwright install",
|
|
16
|
+
"linux": "pip install playwright && playwright install",
|
|
17
|
+
"win32": "pip install playwright && playwright install",
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_install_hint(tool: str) -> str:
|
|
23
|
+
"""Get platform-appropriate install command for a tool.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
tool: Tool name (e.g., "rg", "playwright")
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Install command for the current platform, or generic message if unknown.
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
>>> get_install_hint("rg") # On macOS
|
|
33
|
+
'brew install ripgrep'
|
|
34
|
+
>>> get_install_hint("rg") # On Linux
|
|
35
|
+
'apt install ripgrep # or: snap install ripgrep'
|
|
36
|
+
>>> get_install_hint("rg") # On Windows
|
|
37
|
+
'winget install BurntSushi.ripgrep.MSVC # or: scoop install ripgrep'
|
|
38
|
+
"""
|
|
39
|
+
platform = sys.platform
|
|
40
|
+
# Normalize Linux variants (linux2, etc.)
|
|
41
|
+
if platform.startswith("linux"):
|
|
42
|
+
platform = "linux"
|
|
43
|
+
|
|
44
|
+
commands = INSTALL_COMMANDS.get(tool, {})
|
|
45
|
+
return commands.get(platform, f"Install {tool} for your platform")
|