docs: add present server

This commit is contained in:
Javier Martinez
2026-07-06 14:59:42 +02:00
parent 53e589e802
commit bcbd6a85c3
3 changed files with 102 additions and 7 deletions

View File

@@ -15,7 +15,7 @@ The code execution subsystem provides:
| Type identifier | Role |
|---|---|
| `code_execution_v1` | Top-level wrapper — expands into `bash_v1`, `text_editor_v1`, and optionally `present_files_v1` |
| `code_execution_v1` | Top-level wrapper — expands into `bash_v1`, `text_editor_v1`, and optionally `present_files_v1` and `present_server_v1` |
| `bash_v1` | Run arbitrary bash commands in a sandboxed subprocess |
| `text_editor_v1` | View, create, and edit files in the workspace |
| `view_v1` | View file or directory contents |
@@ -23,11 +23,12 @@ The code execution subsystem provides:
| `create_v1` | Create a new file |
| `insert_v1` | Insert text after a given line |
| `present_files_v1` | Expose generated files to the caller |
| `present_server_v1` | Expose an HTTP service running in the sandbox to the caller |
Standalone endpoints are not provided for code execution tools. Use them through `/v1/messages`.
<Note>
`present_files_v1` is a server-only metadata tool that surfaces created files to API consumers. It is enabled automatically when `code_execution_v1` is used and does not require explicit configuration.
`present_files_v1` and `present_server_v1` are server-only metadata tools that surface generated artifacts and running services to API consumers. They are enabled automatically when `code_execution_v1` is used and do not require explicit configuration.
</Note>
---
@@ -36,7 +37,7 @@ Standalone endpoints are not provided for code execution tools. Use them through
When the model submits a `code_execution_v1` tool call, the server expands it into its constituent tools, creates or reuses a per-session sandbox, and executes the requested operations:
1. **Tool expansion** — `code_execution_v1` expands into `bash_v1`, `text_editor_v1`, and optionally `present_files_v1`
1. **Tool expansion** — `code_execution_v1` expands into `bash_v1`, `text_editor_v1`, and optionally `present_files_v1` and `present_server_v1`
2. **Session management** — each chat session gets an isolated workspace on the host filesystem
3. **Path translation** — canonical LLM-visible paths (`/home/agent/workspace/`) are transparently mapped to real host paths
4. **Subprocess isolation** — bash commands run as child processes with OS-level resource limits (`setrlimit`)
@@ -189,3 +190,23 @@ Code execution sessions are managed automatically:
| Windows | None | None | Subprocesses run without resource limits |
All platforms support the same canonical path translation, output scrubbing, and session lifecycle management.
---
## `present_server` tool
`present_server_v1` exposes an HTTP service running inside the sandbox to the caller. Call it after starting a server (for example a web app, Jupyter, or Streamlit) so the user can open or interact with it.
The model invokes it with:
| Field | Type | Required | Description |
|---|---|---|---|
| `port` | `int` | Yes | Port the service is listening on inside the sandbox. |
| `service_name` | `str` | No | Human-readable name for the service (defaults to `"App"`). |
| `initial_path` | `str \| null` | No | Optional path appended to the URL to deep-link to a specific route. |
The tool result contains a `resource_link` block whose `uri` is the publicly reachable URL of the tunneled service, plus a `text` block describing the service. If the sandbox backend does not support HTTP ingress, the tool returns a `text` block explaining that no endpoint is available.
<Note>
HTTP ingress support depends on the configured `code_execution.provider`. Only sandbox backends that support per-session endpoint tunneling can fulfill `present_server_v1` calls.
</Note>

View File

@@ -55,7 +55,8 @@ These are things not obvious from reading `index.html` that future agents should
- **Scroll fades** — `.chat-list-wrap` and `.messages` both use `mask-image` with `--fade-top-stop`/`--fade-bot-stop` custom properties updated on scroll by `updateChatListFade()` and `updateMessagesFade()`.
- **Toggle switches** — all `input[type="checkbox"]` elements are styled as custom CSS pill toggles with no native appearance.
- **Floating panel frost** — `.modal-card`, `.menu-panel`, and `.model-dropdown` override the shared glass group with a near-solid dark background (`rgba(10,12,22,0.820.94)`), `blur(72px) saturate(1.4)`, and a `to bottom` gradient (lighter at top, denser at bottom) for readability and visual grounding.
- **Code Execution tools** — the Code Execution toggle in the Tools menu sends `{ name: "code_execution", type: "code_execution_v1" }` in the tools array. The backend expands this into `bash`, `text_editor` (view/str_replace/create/insert), and `present_files` (Zylon only). Tool use blocks for these tools render as styled `.code-exec-block` details elements with terminal output, line-numbered file views, diff highlighting, and exit-code badges. Adjacent tool_use + tool_result blocks are combined into a single block via blocks pairing in `blocksToHtml`. The toggle is stored per-chat in `chat.settings.enabledCodeExecution`.
- **Code Execution tools** — the Code Execution toggle in the Tools menu sends `{ name: "code_execution", type: "code_execution_v1" }` in the tools array. The backend expands this into `bash`, `text_editor` (view/str_replace/create/insert), `present_files`, and `present_server`. Tool use blocks for these tools render as styled `.code-exec-block` details elements with terminal output, line-numbered file views, diff highlighting, and exit-code badges. Adjacent tool_use + tool_result blocks are combined into a single block via blocks pairing in `blocksToHtml`. The `isCodeExecTool` allowlist (`bash`, `view`, `str_replace`, `create`, `insert`, `present_files`, `present_server`) drives pairing and per-tool rendering. The toggle is stored per-chat in `chat.settings.enabledCodeExecution`.
- **Code Execution session continuity** — when code execution is enabled, `chat.id` is sent as `container` in `ChatBody` so the backend reuses the same sandbox session across all messages in a chat. The `container` field must be set whenever code execution tools are active.
- **Code Execution file upload** — when Code Execution is enabled, a **Files** button appears in the composer toolbar. It opens a file picker that uploads directly to `POST /v1/files?scope_id={chat.id}` (multipart/form-data). Uploaded files land in the session workspace and are accessible to the model's bash/file tools.
- **Code Execution file downloads** — `present_files` tool results that contain `local_resource` blocks are rendered as `.code-exec-download` anchor elements linking to `GET /v1/files/{file_id}/content?scope_id={chat.id}`. The `file_id` and `mime_type` come from the `local_resource` block schema.
- **Code Execution server links** — `present_server` tool results that contain `resource_link` blocks are rendered as `.code-exec-server-link` anchor elements (globe icon + service name + tunneled URL) opening in a new tab. The `uri`, `name`, and `description` come from the `resource_link` block schema. The tool_use summary shows `service_name:port` (and an optional `initial_path` deep-link).

View File

@@ -2407,6 +2407,11 @@
color: rgba(200, 170, 255, 0.92);
}
.code-exec-block .code-exec-tool-label.server {
background: rgba(112, 183, 255, 0.14);
color: rgba(140, 200, 255, 0.92);
}
/* ── Code execution file downloads ──────────────────────────────────── */
.code-exec-download {
display: inline-flex;
@@ -2447,6 +2452,46 @@
flex: 0 0 auto;
}
/* ── Code execution server links (present_server) ──────────────────── */
.code-exec-server-link {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border: 1px solid rgba(112, 183, 255, 0.22);
border-radius: 10px;
background: rgba(112, 183, 255, 0.08);
color: rgba(140, 200, 255, 0.92);
font-size: 13px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
text-decoration: none;
transition: background 150ms ease, border-color 150ms ease;
cursor: pointer;
width: 100%;
box-sizing: border-box;
}
.code-exec-server-link:hover {
background: rgba(112, 183, 255, 0.16);
border-color: rgba(112, 183, 255, 0.36);
}
.code-exec-server-link svg {
flex: 0 0 auto;
width: 14px;
height: 14px;
}
.code-exec-server-link .code-exec-server-name {
font-weight: 600;
flex: 0 0 auto;
}
.code-exec-server-link .code-exec-server-url {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
opacity: 0.85;
}
/* ── Modal backdrop blur ─────────────────────────────────────────────── */
.modal-backdrop {
backdrop-filter: blur(14px);
@@ -5191,11 +5236,11 @@
}
function isCodeExecTool(name) {
return ["bash", "view", "str_replace", "create", "insert", "present_files"].includes(name);
return ["bash", "view", "str_replace", "create", "insert", "present_files", "present_server"].includes(name);
}
function codeExecToolLabel(name) {
const map = { bash: "Bash", view: "Read", str_replace: "Edit", create: "Create", insert: "Insert", present_files: "Present" };
const map = { bash: "Bash", view: "Read", str_replace: "Edit", create: "Create", insert: "Insert", present_files: "Present", present_server: "Server" };
return map[name] || name;
}
@@ -5205,6 +5250,7 @@
if (name === "create") return "create";
if (name === "str_replace") return "edit";
if (name === "present_files") return "present";
if (name === "present_server") return "server";
return "file";
}
@@ -5231,6 +5277,11 @@
} else if (name === "present_files") {
const files = Array.isArray(input.files) ? input.files : [];
detail = `<div class="code-exec-present-files">${files.map(f => `<div class="code-exec-present-file"><span class="file-icon">${icons.file}</span><span class="file-name">${escapeHtml(typeof f === "string" ? f : f.path || f.name || "")}</span></div>`).join("")}</div>`;
} else if (name === "present_server") {
const port = input.port != null ? `:${input.port}` : "";
const service = input.service_name || "App";
const initialPath = input.initial_path ? `${input.initial_path}` : "";
detail = `<div class="code-exec-file-info">${icons.globe}<span class="code-exec-file-path">${escapeHtml(service)}${escapeHtml(port)}${escapeHtml(initialPath)}</span></div>`;
}
// If we have a result block, render the result content in the same body
@@ -5279,6 +5330,28 @@
} else {
resultHtml = rawText ? `<div class="code-exec-terminal">${escapeHtml(rawText)}</div>` : "";
}
} else if (name === "present_server") {
const content = resultBlock.content;
if (Array.isArray(content)) {
const linkItems = content.filter(b => b?.type === "resource_link");
const textItems = content.filter(b => b?.type === "text" || typeof b === "string");
const textHtml = textItems.map(b => escapeHtml(typeof b === "string" ? b : (b.text || ""))).join(" ");
if (linkItems.length > 0) {
resultHtml = `<div class="code-exec-present-files">${linkItems.map(l => {
const url = l.uri || "";
const svc = l.name || "App";
const desc = l.description ? escapeHtml(l.description) : "";
if (url) {
return `<a class="code-exec-server-link" href="${escapeAttr(url)}" target="_blank" rel="noreferrer">${icons.globe}<span class="code-exec-server-name">${escapeHtml(svc)}</span><span class="code-exec-server-url">${escapeHtml(url)}</span></a>${desc ? `<div class="code-exec-terminal" style="margin-top:6px">${desc}</div>` : ""}`;
}
return `<div class="code-exec-present-file"><span class="file-icon">${icons.globe}</span><span class="file-name">${escapeHtml(svc)}</span></div>`;
}).join("")}</div>${textHtml ? `<div class="code-exec-terminal" style="margin-top:8px">${textHtml}</div>` : ""}`;
} else {
resultHtml = textHtml ? `<div class="code-exec-terminal">${textHtml}</div>` : "";
}
} else {
resultHtml = rawText ? `<div class="code-exec-terminal">${escapeHtml(rawText)}</div>` : "";
}
} else {
resultHtml = renderFileView(rawText);
}
@@ -5287,7 +5360,7 @@
}
}
const summaryLabel = name === "bash" ? (input.command ? input.command.split("\n")[0].slice(0, 60) + (input.command.length > 60 ? "…" : "") : "") : (input.path || "");
const summaryLabel = name === "bash" ? (input.command ? input.command.split("\n")[0].slice(0, 60) + (input.command.length > 60 ? "…" : "") : "") : name === "present_server" ? `${input.service_name || "App"}${input.port != null ? `:${input.port}` : ""}` : (input.path || "");
return `<details class="code-exec-block"${resultBlock ? " open" : ""}><summary><span class="code-exec-tool-label ${labelClass}">${label}</span>${escapeHtml(summaryLabel)}</summary><div class="code-exec-body">${detail}</div></details>`;
}