akseljoonas HF Staff commited on
Commit
555a204
Β·
1 Parent(s): 829ae5c

feat: resolve sandbox file paths for hf_jobs script display

Browse files

- Extract resolve_sandbox_script utility in sandbox_tool.py to detect
file paths and read content from sandbox via cat
- Resolve script paths before sending approval_required event so the
frontend receives actual file content for display and editing
- Show sandbox bash tool description arg instead of tool name in UI

agent/core/agent_loop.py CHANGED
@@ -491,6 +491,16 @@ class Handlers:
491
  tool_args = json.loads(tc.function.arguments)
492
  except (json.JSONDecodeError, TypeError):
493
  tool_args = {}
 
 
 
 
 
 
 
 
 
 
494
  tools_data.append(
495
  {
496
  "tool": tool_name,
 
491
  tool_args = json.loads(tc.function.arguments)
492
  except (json.JSONDecodeError, TypeError):
493
  tool_args = {}
494
+
495
+ # Resolve sandbox file paths for hf_jobs scripts so the
496
+ # frontend can display & edit the actual file content.
497
+ if tool_name == "hf_jobs" and isinstance(tool_args.get("script"), str):
498
+ from agent.tools.sandbox_tool import resolve_sandbox_script
499
+ sandbox = getattr(session, "sandbox", None)
500
+ content, _ = await resolve_sandbox_script(sandbox, tool_args["script"])
501
+ if content:
502
+ tool_args = {**tool_args, "script": content}
503
+
504
  tools_data.append(
505
  {
506
  "tool": tool_name,
agent/tools/sandbox_tool.py CHANGED
@@ -12,6 +12,7 @@ a cpu-basic sandbox is auto-created (no approval needed).
12
  from __future__ import annotations
13
 
14
  import asyncio
 
15
  from typing import Any
16
 
17
  from huggingface_hub import HfApi, SpaceHardware
@@ -19,6 +20,37 @@ from huggingface_hub import HfApi, SpaceHardware
19
  from agent.core.session import Event
20
  from agent.tools.sandbox_client import Sandbox
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  # ── Tool name mapping (short agent names β†’ Sandbox client names) ──────
23
 
24
 
 
12
  from __future__ import annotations
13
 
14
  import asyncio
15
+ import shlex
16
  from typing import Any
17
 
18
  from huggingface_hub import HfApi, SpaceHardware
 
20
  from agent.core.session import Event
21
  from agent.tools.sandbox_client import Sandbox
22
 
23
+
24
+ def _looks_like_path(script: str) -> bool:
25
+ """Return True if the script string looks like a file path (not inline code)."""
26
+ return (
27
+ isinstance(script, str)
28
+ and script.strip() == script
29
+ and not any(c in script for c in "\r\n\0")
30
+ and (script.startswith("/") or script.startswith("./") or script.startswith("../"))
31
+ )
32
+
33
+
34
+ async def resolve_sandbox_script(sandbox: Any, script: str) -> tuple[str | None, str | None]:
35
+ """Read a file from the sandbox if *script* looks like a path.
36
+
37
+ Returns:
38
+ (content, error) β€” content is the file text on success,
39
+ error is a message on failure. Both None means *script*
40
+ is not a path (caller should use it as-is).
41
+ """
42
+ if not sandbox or not _looks_like_path(script):
43
+ return None, None
44
+ try:
45
+ result = await asyncio.to_thread(
46
+ sandbox.bash, f"cat {shlex.quote(script)}"
47
+ )
48
+ if result.success and result.output:
49
+ return result.output, None
50
+ return None, f"Failed to read {script} from sandbox: {result.error}"
51
+ except Exception as e:
52
+ return None, f"Failed to read {script} from sandbox: {e}"
53
+
54
  # ── Tool name mapping (short agent names β†’ Sandbox client names) ──────
55
 
56
 
frontend/src/components/Chat/ToolCallGroup.tsx CHANGED
@@ -546,7 +546,7 @@ export default function ToolCallGroup({ tools, approveTools }: ToolCallGroupProp
546
  whiteSpace: 'nowrap',
547
  }}
548
  >
549
- {toolDisplayMap[tool.toolCallId] || tool.toolName}
550
  </Typography>
551
 
552
  {/* Status chip (non hf_jobs, or hf_jobs without final status) */}
 
546
  whiteSpace: 'nowrap',
547
  }}
548
  >
549
+ {toolDisplayMap[tool.toolCallId] || String((tool.input as Record<string, unknown>)?.description || '') || tool.toolName}
550
  </Typography>
551
 
552
  {/* Status chip (non hf_jobs, or hf_jobs without final status) */}