glaip-sdk 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- glaip_sdk/_version.py +1 -3
- glaip_sdk/branding.py +2 -6
- glaip_sdk/cli/agent_config.py +2 -6
- glaip_sdk/cli/auth.py +11 -30
- glaip_sdk/cli/commands/agents.py +45 -107
- glaip_sdk/cli/commands/configure.py +12 -36
- glaip_sdk/cli/commands/mcps.py +26 -63
- glaip_sdk/cli/commands/models.py +2 -4
- glaip_sdk/cli/commands/tools.py +22 -35
- glaip_sdk/cli/commands/update.py +3 -8
- glaip_sdk/cli/config.py +1 -3
- glaip_sdk/cli/display.py +4 -12
- glaip_sdk/cli/io.py +8 -14
- glaip_sdk/cli/main.py +10 -30
- glaip_sdk/cli/mcp_validators.py +5 -15
- glaip_sdk/cli/pager.py +3 -9
- glaip_sdk/cli/parsers/json_input.py +11 -22
- glaip_sdk/cli/resolution.py +3 -9
- glaip_sdk/cli/rich_helpers.py +1 -3
- glaip_sdk/cli/slash/agent_session.py +5 -10
- glaip_sdk/cli/slash/prompt.py +3 -10
- glaip_sdk/cli/slash/session.py +46 -95
- glaip_sdk/cli/transcript/cache.py +6 -19
- glaip_sdk/cli/transcript/capture.py +6 -20
- glaip_sdk/cli/transcript/launcher.py +1 -3
- glaip_sdk/cli/transcript/viewer.py +11 -40
- glaip_sdk/cli/update_notifier.py +165 -21
- glaip_sdk/cli/utils.py +33 -84
- glaip_sdk/cli/validators.py +11 -12
- glaip_sdk/client/_agent_payloads.py +10 -30
- glaip_sdk/client/agents.py +33 -63
- glaip_sdk/client/base.py +77 -35
- glaip_sdk/client/mcps.py +1 -3
- glaip_sdk/client/run_rendering.py +6 -14
- glaip_sdk/client/tools.py +8 -24
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/exceptions.py +1 -3
- glaip_sdk/models.py +14 -33
- glaip_sdk/payload_schemas/agent.py +1 -3
- glaip_sdk/utils/agent_config.py +4 -14
- glaip_sdk/utils/client_utils.py +7 -21
- glaip_sdk/utils/display.py +2 -6
- glaip_sdk/utils/general.py +1 -3
- glaip_sdk/utils/import_export.py +3 -9
- glaip_sdk/utils/rendering/formatting.py +2 -5
- glaip_sdk/utils/rendering/models.py +2 -6
- glaip_sdk/utils/rendering/renderer/__init__.py +1 -3
- glaip_sdk/utils/rendering/renderer/base.py +63 -189
- glaip_sdk/utils/rendering/renderer/debug.py +4 -14
- glaip_sdk/utils/rendering/renderer/panels.py +1 -3
- glaip_sdk/utils/rendering/renderer/progress.py +3 -11
- glaip_sdk/utils/rendering/renderer/stream.py +7 -19
- glaip_sdk/utils/rendering/renderer/toggle.py +1 -3
- glaip_sdk/utils/rendering/step_tree_state.py +1 -3
- glaip_sdk/utils/rendering/steps.py +29 -83
- glaip_sdk/utils/resource_refs.py +4 -13
- glaip_sdk/utils/serialization.py +14 -46
- glaip_sdk/utils/validation.py +4 -4
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.2.dist-info}/METADATA +1 -1
- glaip_sdk-0.1.2.dist-info/RECORD +82 -0
- glaip_sdk-0.1.0.dist-info/RECORD +0 -82
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.2.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.1.2.dist-info}/entry_points.txt +0 -0
glaip_sdk/exceptions.py
CHANGED
|
@@ -107,9 +107,7 @@ class AgentTimeoutError(TimeoutError):
|
|
|
107
107
|
agent_name: Optional name of the agent that timed out
|
|
108
108
|
"""
|
|
109
109
|
agent_info = f" for agent '{agent_name}'" if agent_name else ""
|
|
110
|
-
message =
|
|
111
|
-
f"Agent execution timed out after {timeout_seconds} seconds{agent_info}"
|
|
112
|
-
)
|
|
110
|
+
message = f"Agent execution timed out after {timeout_seconds} seconds{agent_info}"
|
|
113
111
|
super().__init__(message)
|
|
114
112
|
self.timeout_seconds = timeout_seconds
|
|
115
113
|
self.agent_name = agent_name
|
glaip_sdk/models.py
CHANGED
|
@@ -13,6 +13,9 @@ from pydantic import BaseModel
|
|
|
13
13
|
|
|
14
14
|
from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT
|
|
15
15
|
|
|
16
|
+
_AGENT_CLIENT_REQUIRED_MSG = "No client available. Use client.get_agent_by_id() to get a client-connected agent."
|
|
17
|
+
_MCP_CLIENT_REQUIRED_MSG = "No client available. Use client.get_mcp_by_id() to get a client-connected MCP."
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|
class Agent(BaseModel):
|
|
18
21
|
"""Agent model for API responses."""
|
|
@@ -27,9 +30,7 @@ class Agent(BaseModel):
|
|
|
27
30
|
tools: list[dict[str, Any]] | None = None # Backend returns ToolReference objects
|
|
28
31
|
agents: list[dict[str, Any]] | None = None # Backend returns AgentReference objects
|
|
29
32
|
mcps: list[dict[str, Any]] | None = None # Backend returns MCPReference objects
|
|
30
|
-
tool_configs: dict[str, Any] | None =
|
|
31
|
-
None # Backend returns tool configurations keyed by tool UUID
|
|
32
|
-
)
|
|
33
|
+
tool_configs: dict[str, Any] | None = None # Backend returns tool configurations keyed by tool UUID
|
|
33
34
|
agent_config: dict[str, Any] | None = None
|
|
34
35
|
timeout: int = DEFAULT_AGENT_RUN_TIMEOUT
|
|
35
36
|
metadata: dict[str, Any] | None = None
|
|
@@ -53,9 +54,7 @@ class Agent(BaseModel):
|
|
|
53
54
|
**kwargs: Additional arguments passed to run_agent
|
|
54
55
|
"""
|
|
55
56
|
if not self._client:
|
|
56
|
-
raise RuntimeError(
|
|
57
|
-
"No client available. Use client.get_agent_by_id() to get a client-connected agent."
|
|
58
|
-
)
|
|
57
|
+
raise RuntimeError(_AGENT_CLIENT_REQUIRED_MSG)
|
|
59
58
|
# Automatically pass the agent name for better renderer display
|
|
60
59
|
kwargs.setdefault("agent_name", self.name)
|
|
61
60
|
# Pass the agent's configured timeout if not explicitly overridden
|
|
@@ -80,9 +79,7 @@ class Agent(BaseModel):
|
|
|
80
79
|
Exception: For other unexpected errors
|
|
81
80
|
"""
|
|
82
81
|
if not self._client:
|
|
83
|
-
raise RuntimeError(
|
|
84
|
-
"No client available. Use client.get_agent_by_id() to get a client-connected agent."
|
|
85
|
-
)
|
|
82
|
+
raise RuntimeError(_AGENT_CLIENT_REQUIRED_MSG)
|
|
86
83
|
# Automatically pass the agent name for better context
|
|
87
84
|
kwargs.setdefault("agent_name", self.name)
|
|
88
85
|
# Pass the agent's configured timeout if not explicitly overridden
|
|
@@ -95,9 +92,7 @@ class Agent(BaseModel):
|
|
|
95
92
|
def update(self, **kwargs) -> "Agent":
|
|
96
93
|
"""Update agent attributes."""
|
|
97
94
|
if not self._client:
|
|
98
|
-
raise RuntimeError(
|
|
99
|
-
"No client available. Use client.get_agent_by_id() to get a client-connected agent."
|
|
100
|
-
)
|
|
95
|
+
raise RuntimeError(_AGENT_CLIENT_REQUIRED_MSG)
|
|
101
96
|
updated_agent = self._client.update_agent(self.id, **kwargs)
|
|
102
97
|
# Update current instance with new data
|
|
103
98
|
for key, value in updated_agent.model_dump().items():
|
|
@@ -108,9 +103,7 @@ class Agent(BaseModel):
|
|
|
108
103
|
def delete(self) -> None:
|
|
109
104
|
"""Delete the agent."""
|
|
110
105
|
if not self._client:
|
|
111
|
-
raise RuntimeError(
|
|
112
|
-
"No client available. Use client.get_agent_by_id() to get a client-connected agent."
|
|
113
|
-
)
|
|
106
|
+
raise RuntimeError(_AGENT_CLIENT_REQUIRED_MSG)
|
|
114
107
|
self._client.delete_agent(self.id)
|
|
115
108
|
|
|
116
109
|
|
|
@@ -149,16 +142,12 @@ class Tool(BaseModel):
|
|
|
149
142
|
Pass 'file' parameter to update tool code via file upload.
|
|
150
143
|
"""
|
|
151
144
|
if not self._client:
|
|
152
|
-
raise RuntimeError(
|
|
153
|
-
"No client available. Use client.get_tool_by_id() to get a client-connected tool."
|
|
154
|
-
)
|
|
145
|
+
raise RuntimeError("No client available. Use client.get_tool_by_id() to get a client-connected tool.")
|
|
155
146
|
|
|
156
147
|
# Check if file upload is requested
|
|
157
148
|
if "file" in kwargs:
|
|
158
149
|
file_path = kwargs.pop("file") # Remove file from kwargs for metadata
|
|
159
|
-
updated_tool = self._client.tools.update_tool_via_file(
|
|
160
|
-
self.id, file_path, **kwargs
|
|
161
|
-
)
|
|
150
|
+
updated_tool = self._client.tools.update_tool_via_file(self.id, file_path, **kwargs)
|
|
162
151
|
else:
|
|
163
152
|
# Regular metadata update
|
|
164
153
|
updated_tool = self._client.tools.update_tool(self.id, **kwargs)
|
|
@@ -172,9 +161,7 @@ class Tool(BaseModel):
|
|
|
172
161
|
def delete(self) -> None:
|
|
173
162
|
"""Delete the tool."""
|
|
174
163
|
if not self._client:
|
|
175
|
-
raise RuntimeError(
|
|
176
|
-
"No client available. Use client.get_tool_by_id() to get a client-connected tool."
|
|
177
|
-
)
|
|
164
|
+
raise RuntimeError("No client available. Use client.get_tool_by_id() to get a client-connected tool.")
|
|
178
165
|
self._client.delete_tool(self.id)
|
|
179
166
|
|
|
180
167
|
|
|
@@ -198,9 +185,7 @@ class MCP(BaseModel):
|
|
|
198
185
|
def get_tools(self) -> list[dict[str, Any]]:
|
|
199
186
|
"""Get tools available from this MCP."""
|
|
200
187
|
if not self._client:
|
|
201
|
-
raise RuntimeError(
|
|
202
|
-
"No client available. Use client.get_mcp_by_id() to get a client-connected MCP."
|
|
203
|
-
)
|
|
188
|
+
raise RuntimeError(_MCP_CLIENT_REQUIRED_MSG)
|
|
204
189
|
# This would delegate to the client's MCP tools endpoint
|
|
205
190
|
# For now, return empty list as placeholder
|
|
206
191
|
return []
|
|
@@ -208,9 +193,7 @@ class MCP(BaseModel):
|
|
|
208
193
|
def update(self, **kwargs) -> "MCP":
|
|
209
194
|
"""Update MCP attributes."""
|
|
210
195
|
if not self._client:
|
|
211
|
-
raise RuntimeError(
|
|
212
|
-
"No client available. Use client.get_mcp_by_id() to get a client-connected MCP."
|
|
213
|
-
)
|
|
196
|
+
raise RuntimeError(_MCP_CLIENT_REQUIRED_MSG)
|
|
214
197
|
updated_mcp = self._client.update_mcp(self.id, **kwargs)
|
|
215
198
|
# Update current instance with new data
|
|
216
199
|
for key, value in updated_mcp.model_dump().items():
|
|
@@ -221,9 +204,7 @@ class MCP(BaseModel):
|
|
|
221
204
|
def delete(self) -> None:
|
|
222
205
|
"""Delete the MCP."""
|
|
223
206
|
if not self._client:
|
|
224
|
-
raise RuntimeError(
|
|
225
|
-
"No client available. Use client.get_mcp_by_id() to get a client-connected MCP."
|
|
226
|
-
)
|
|
207
|
+
raise RuntimeError("No client available. Use client.get_mcp_by_id() to get a client-connected MCP.")
|
|
227
208
|
self._client.delete_mcp(self.id)
|
|
228
209
|
|
|
229
210
|
|
|
@@ -60,9 +60,7 @@ AGENT_FIELD_RULES: Mapping[str, FieldRule] = {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
def get_import_field_plan(
|
|
64
|
-
field_name: str, operation: AgentImportOperation
|
|
65
|
-
) -> ImportFieldPlan:
|
|
63
|
+
def get_import_field_plan(field_name: str, operation: AgentImportOperation) -> ImportFieldPlan:
|
|
66
64
|
"""Return the import handling plan for ``field_name`` under ``operation``.
|
|
67
65
|
|
|
68
66
|
Unknown fields default to being copied as-is so new API fields propagate
|
glaip_sdk/utils/agent_config.py
CHANGED
|
@@ -54,17 +54,11 @@ def sanitize_agent_config(
|
|
|
54
54
|
cfg = agent_config or {}
|
|
55
55
|
|
|
56
56
|
if strip_lm_identity and isinstance(cfg, dict):
|
|
57
|
-
cfg = {
|
|
58
|
-
k: v
|
|
59
|
-
for k, v in cfg.items()
|
|
60
|
-
if k not in {"lm_provider", "lm_name", "lm_base_url"}
|
|
61
|
-
}
|
|
57
|
+
cfg = {k: v for k, v in cfg.items() if k not in {"lm_provider", "lm_name", "lm_base_url"}}
|
|
62
58
|
return cfg
|
|
63
59
|
|
|
64
60
|
|
|
65
|
-
def resolve_language_model_selection(
|
|
66
|
-
merged_data: dict[str, Any], cli_model: str | None
|
|
67
|
-
) -> tuple[dict[str, Any], bool]:
|
|
61
|
+
def resolve_language_model_selection(merged_data: dict[str, Any], cli_model: str | None) -> tuple[dict[str, Any], bool]:
|
|
68
62
|
"""Resolve language model selection from merged data and CLI args.
|
|
69
63
|
|
|
70
64
|
Implements the LM selection priority:
|
|
@@ -98,17 +92,13 @@ def resolve_language_model_selection(
|
|
|
98
92
|
# Priority 3: Legacy lm_name from agent_config
|
|
99
93
|
agent_config = merged_data.get("agent_config") or {}
|
|
100
94
|
if isinstance(agent_config, dict) and agent_config.get("lm_name"):
|
|
101
|
-
return {
|
|
102
|
-
"model": agent_config["lm_name"]
|
|
103
|
-
}, True # Strip LM identity when extracting from agent_config
|
|
95
|
+
return {"model": agent_config["lm_name"]}, True # Strip LM identity when extracting from agent_config
|
|
104
96
|
|
|
105
97
|
# No LM selection found
|
|
106
98
|
return {}, False
|
|
107
99
|
|
|
108
100
|
|
|
109
|
-
def normalize_agent_config_for_import(
|
|
110
|
-
agent_data: dict[str, Any], cli_model: str | None = None
|
|
111
|
-
) -> dict[str, Any]:
|
|
101
|
+
def normalize_agent_config_for_import(agent_data: dict[str, Any], cli_model: str | None = None) -> dict[str, Any]:
|
|
112
102
|
"""Automatically normalize agent configuration by extracting LM settings from agent_config.
|
|
113
103
|
|
|
114
104
|
This function addresses the common issue where exported agent configurations contain
|
glaip_sdk/utils/client_utils.py
CHANGED
|
@@ -90,9 +90,7 @@ def extract_ids(items: list[str | Any] | None) -> list[str] | None:
|
|
|
90
90
|
return result if result else None
|
|
91
91
|
|
|
92
92
|
|
|
93
|
-
def create_model_instances(
|
|
94
|
-
data: list[dict] | None, model_class: type, client: Any
|
|
95
|
-
) -> list[Any]:
|
|
93
|
+
def create_model_instances(data: list[dict] | None, model_class: type, client: Any) -> list[Any]:
|
|
96
94
|
"""Create model instances from API data with client association.
|
|
97
95
|
|
|
98
96
|
This is a common pattern used across different clients (agents, tools, mcps)
|
|
@@ -112,9 +110,7 @@ def create_model_instances(
|
|
|
112
110
|
return [model_class(**item_data)._set_client(client) for item_data in data]
|
|
113
111
|
|
|
114
112
|
|
|
115
|
-
def find_by_name(
|
|
116
|
-
items: list[Any], name: str, case_sensitive: bool = False
|
|
117
|
-
) -> list[Any]:
|
|
113
|
+
def find_by_name(items: list[Any], name: str, case_sensitive: bool = False) -> list[Any]:
|
|
118
114
|
"""Filter items by name with optional case sensitivity.
|
|
119
115
|
|
|
120
116
|
This is a common pattern used across different clients for client-side
|
|
@@ -234,9 +230,7 @@ def _handle_streaming_error(
|
|
|
234
230
|
if isinstance(e, httpx.ReadTimeout):
|
|
235
231
|
logger.error(f"Read timeout during streaming: {e}")
|
|
236
232
|
logger.error("This usually indicates the backend is taking too long to respond")
|
|
237
|
-
logger.error(
|
|
238
|
-
"Consider increasing the timeout value or checking backend performance"
|
|
239
|
-
)
|
|
233
|
+
logger.error("Consider increasing the timeout value or checking backend performance")
|
|
240
234
|
raise AgentTimeoutError(timeout_seconds or 30.0, agent_name)
|
|
241
235
|
|
|
242
236
|
elif isinstance(e, httpx.TimeoutException):
|
|
@@ -275,9 +269,7 @@ def _yield_event_data(event_data: dict[str, Any] | None) -> Iterator[dict[str, A
|
|
|
275
269
|
yield event_data
|
|
276
270
|
|
|
277
271
|
|
|
278
|
-
def _flush_remaining_buffer(
|
|
279
|
-
buf: list[str], event_type: str | None, event_id: str | None
|
|
280
|
-
) -> Iterator[dict[str, Any]]:
|
|
272
|
+
def _flush_remaining_buffer(buf: list[str], event_type: str | None, event_id: str | None) -> Iterator[dict[str, Any]]:
|
|
281
273
|
"""Flush any remaining data in buffer."""
|
|
282
274
|
if buf:
|
|
283
275
|
yield {
|
|
@@ -317,9 +309,7 @@ def iter_sse_events(
|
|
|
317
309
|
if line is None:
|
|
318
310
|
continue
|
|
319
311
|
|
|
320
|
-
buf, event_type, event_id, event_data, completed = _process_sse_line(
|
|
321
|
-
line, buf, event_type, event_id
|
|
322
|
-
)
|
|
312
|
+
buf, event_type, event_id, event_data, completed = _process_sse_line(line, buf, event_type, event_id)
|
|
323
313
|
|
|
324
314
|
yield from _yield_event_data(event_data)
|
|
325
315
|
if completed:
|
|
@@ -385,9 +375,7 @@ def _create_form_data(message: str) -> dict[str, Any]:
|
|
|
385
375
|
return {"input": message, "message": message, "stream": True}
|
|
386
376
|
|
|
387
377
|
|
|
388
|
-
def _prepare_file_entry(
|
|
389
|
-
item: str | BinaryIO, stack: ExitStack
|
|
390
|
-
) -> tuple[str, tuple[str, BinaryIO, str]]:
|
|
378
|
+
def _prepare_file_entry(item: str | BinaryIO, stack: ExitStack) -> tuple[str, tuple[str, BinaryIO, str]]:
|
|
391
379
|
"""Prepare a single file entry for multipart data."""
|
|
392
380
|
if isinstance(item, str):
|
|
393
381
|
return _prepare_path_entry(item, stack)
|
|
@@ -395,9 +383,7 @@ def _prepare_file_entry(
|
|
|
395
383
|
return _prepare_stream_entry(item)
|
|
396
384
|
|
|
397
385
|
|
|
398
|
-
def _prepare_path_entry(
|
|
399
|
-
path_str: str, stack: ExitStack
|
|
400
|
-
) -> tuple[str, tuple[str, BinaryIO, str]]:
|
|
386
|
+
def _prepare_path_entry(path_str: str, stack: ExitStack) -> tuple[str, tuple[str, BinaryIO, str]]:
|
|
401
387
|
"""Prepare a file path entry."""
|
|
402
388
|
file_path = Path(path_str)
|
|
403
389
|
if not file_path.exists():
|
glaip_sdk/utils/display.py
CHANGED
|
@@ -109,9 +109,7 @@ def print_agent_updated(agent: Any) -> None:
|
|
|
109
109
|
"""
|
|
110
110
|
if RICH_AVAILABLE:
|
|
111
111
|
console = _create_console()
|
|
112
|
-
console.print(
|
|
113
|
-
f"[{SUCCESS_STYLE}]✅ Agent '{agent.name}' updated successfully[/]"
|
|
114
|
-
)
|
|
112
|
+
console.print(f"[{SUCCESS_STYLE}]✅ Agent '{agent.name}' updated successfully[/]")
|
|
115
113
|
else:
|
|
116
114
|
print(f"✅ Agent '{agent.name}' updated successfully")
|
|
117
115
|
|
|
@@ -124,8 +122,6 @@ def print_agent_deleted(agent_id: str) -> None:
|
|
|
124
122
|
"""
|
|
125
123
|
if RICH_AVAILABLE:
|
|
126
124
|
console = _create_console()
|
|
127
|
-
console.print(
|
|
128
|
-
f"[{SUCCESS_STYLE}]✅ Agent deleted successfully (ID: {agent_id})[/]"
|
|
129
|
-
)
|
|
125
|
+
console.print(f"[{SUCCESS_STYLE}]✅ Agent deleted successfully (ID: {agent_id})[/]")
|
|
130
126
|
else:
|
|
131
127
|
print(f"✅ Agent deleted successfully (ID: {agent_id})")
|
glaip_sdk/utils/general.py
CHANGED
|
@@ -76,9 +76,7 @@ def format_datetime(dt: datetime | str | None) -> str:
|
|
|
76
76
|
return str(dt)
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
def progress_bar(
|
|
80
|
-
iterable: Iterable[Any], description: str = "Processing"
|
|
81
|
-
) -> Iterator[Any]:
|
|
79
|
+
def progress_bar(iterable: Iterable[Any], description: str = "Processing") -> Iterator[Any]:
|
|
82
80
|
"""Simple progress bar using click.
|
|
83
81
|
|
|
84
82
|
Args:
|
glaip_sdk/utils/import_export.py
CHANGED
|
@@ -71,14 +71,10 @@ def _get_default_array_fields() -> list[str]:
|
|
|
71
71
|
|
|
72
72
|
def _should_use_cli_value(cli_value: Any) -> bool:
|
|
73
73
|
"""Check if CLI value should be used."""
|
|
74
|
-
return cli_value is not None and (
|
|
75
|
-
not isinstance(cli_value, list | tuple) or len(cli_value) > 0
|
|
76
|
-
)
|
|
74
|
+
return cli_value is not None and (not isinstance(cli_value, (list, tuple)) or len(cli_value) > 0)
|
|
77
75
|
|
|
78
76
|
|
|
79
|
-
def _handle_array_field_merge(
|
|
80
|
-
key: str, cli_value: Any, import_data: dict[str, Any]
|
|
81
|
-
) -> Any:
|
|
77
|
+
def _handle_array_field_merge(key: str, cli_value: Any, import_data: dict[str, Any]) -> Any:
|
|
82
78
|
"""Handle merging of array fields."""
|
|
83
79
|
import_value = import_data[key]
|
|
84
80
|
if isinstance(import_value, list):
|
|
@@ -107,9 +103,7 @@ def _merge_cli_values_with_import(
|
|
|
107
103
|
merged[key] = import_data[key]
|
|
108
104
|
|
|
109
105
|
|
|
110
|
-
def _add_import_only_fields(
|
|
111
|
-
merged: dict[str, Any], import_data: dict[str, Any]
|
|
112
|
-
) -> None:
|
|
106
|
+
def _add_import_only_fields(merged: dict[str, Any], import_data: dict[str, Any]) -> None:
|
|
113
107
|
"""Add fields that exist only in import data."""
|
|
114
108
|
for key, import_value in import_data.items():
|
|
115
109
|
if key not in merged:
|
|
@@ -123,15 +123,12 @@ def _redact_string_content(text: str) -> str:
|
|
|
123
123
|
def _is_sensitive_key(key: str) -> bool:
|
|
124
124
|
"""Check if a key contains sensitive information."""
|
|
125
125
|
key_lower = key.lower()
|
|
126
|
-
return any(
|
|
127
|
-
sensitive in key_lower
|
|
128
|
-
for sensitive in ["password", "secret", "token", "key", "api_key"]
|
|
129
|
-
)
|
|
126
|
+
return any(sensitive in key_lower for sensitive in ["password", "secret", "token", "key", "api_key"])
|
|
130
127
|
|
|
131
128
|
|
|
132
129
|
def _should_recurse_redaction(value: Any) -> bool:
|
|
133
130
|
"""Check if a value should be recursively processed."""
|
|
134
|
-
return isinstance(value, dict
|
|
131
|
+
return isinstance(value, (dict, list)) or isinstance(value, str)
|
|
135
132
|
|
|
136
133
|
|
|
137
134
|
def glyph_for_status(icon_key: str | None) -> str | None:
|
|
@@ -48,7 +48,7 @@ class Step:
|
|
|
48
48
|
source: Optional duration source tag
|
|
49
49
|
"""
|
|
50
50
|
self.duration_unknown = False
|
|
51
|
-
if isinstance(duration_raw, int
|
|
51
|
+
if isinstance(duration_raw, (int, float)) and duration_raw > 0:
|
|
52
52
|
# Use provided duration if it's a positive number (even if very small)
|
|
53
53
|
self.duration_ms = round(float(duration_raw) * 1000)
|
|
54
54
|
self.duration_source = source or self.duration_source or "provided"
|
|
@@ -81,8 +81,4 @@ class RunStats:
|
|
|
81
81
|
Returns:
|
|
82
82
|
Duration in seconds if run is finished, None otherwise
|
|
83
83
|
"""
|
|
84
|
-
return (
|
|
85
|
-
None
|
|
86
|
-
if self.finished_at is None
|
|
87
|
-
else round(self.finished_at - self.started_at, 2)
|
|
88
|
-
)
|
|
84
|
+
return None if self.finished_at is None else round(self.finished_at - self.started_at, 2)
|
|
@@ -37,9 +37,7 @@ def make_silent_renderer() -> RichStreamRenderer:
|
|
|
37
37
|
persist_live=False,
|
|
38
38
|
render_thinking=False,
|
|
39
39
|
)
|
|
40
|
-
return RichStreamRenderer(
|
|
41
|
-
console=Console(file=io.StringIO(), force_terminal=False), cfg=cfg
|
|
42
|
-
)
|
|
40
|
+
return RichStreamRenderer(console=Console(file=io.StringIO(), force_terminal=False), cfg=cfg)
|
|
43
41
|
|
|
44
42
|
|
|
45
43
|
def make_minimal_renderer() -> RichStreamRenderer:
|