智能体
developers.cloudflare.com ·
@Cloudflare Developers
Cloudflare Agents SDK 新增内部主机名:无需公网 URL 即可在浏览器预览开发服务器
Cloudflare Agents SDK 引入 internal hostnames 能力,让 Agent 的持久化浏览器可访问仅由 Agent 自身提供的站点(如 preview.internal),请求被路由到 Durable Object 而非公网。每个内部主机带 fetch 函数,可使用对象绑定与状态,也可通过 service binding 直连其他 Worker;支持 target schema 让模型选择端口或目录。该能力独立于 Agent harness,提供 Pi Durable、AI SDK、TanStack AI 适配器,需 Agents SDK v0.31.0 及以上。
AI推荐理由 Cloudflare Agents SDK 官方文档更新,给出 internal hostnames 的具体机制(browser.assignHost、fetchInternalHost、service binding 直连 Worker)和可运行代码示例,信息密度高、来源可信。局限是仅为预览能力,未提供性能或安全验证数据,且需 Agents SDK v0.31.0 以上。
78
正文
Agents that build websites need to check their work in a real browser. Internal hostnames let your agent's browser preview a dev server running in a container without exposing that server to the public Internet.
With internal hostnames, the [persistent browser](https://developers.cloudflare.com/agents/tools/browser/) in the [Cloudflare Agents SDK](https://developers.cloudflare.com/agents/) can open sites that only your agent serves. Give the browser hostnames such as `preview.internal`, and its requests to them go to your agent instead of the Internet. The model can check a dev server in its sandbox, a folder in its workspace, or another Worker in the same browser it uses for the web, and nothing is exposed publicly.

Each internal host has a `fetch` function that runs in your Durable Object, so it can use the object's bindings and state. A host can also send its requests straight to another Worker through a service binding. Add a `target` schema to a host, and the model chooses what it serves, such as which port or which folder.
Internal hostnames are a capability of the browser, independent of your agent harness. Use the built-in adapters for Pi Durable, AI SDK, and TanStack AI, or connect another harness by implementing a tool with [`browser.session()`](https://developers.cloudflare.com/agents/tools/browser/#use-another-harness). No prebuilt adapter is required.
This example adds a private dev-server preview to a [Pi Durable agent](https://developers.cloudflare.com/agents/tools/browser/#use-it-with-pi-durable). Start with that guide's packages, bindings, and request handler, and install [Agents SDK v0.31.0 ↗︎](https://github.com/cloudflare/agents/releases/tag/agents%400.31.0) or later. The example has three parts:
* **Browser configuration:** `internalHosts` defines how the browser reaches the dev server.
* **Durable Object wiring:** Lifecycle and `fetchInternalHost()` connect the browser to your application.
* **Harness integration:** `browserTool` from `agents/browser/pi` registers the tool with Pi.
```
import { DurableObject } from "cloudflare:workers";
import { createModels } from "@earendil-works/pi-ai/models";
import { createRegistry, Harness } from "@earendil-works/pi-durable";
import { Browser, browserRun } from "agents/browser";
import { browserTool } from "agents/browser/pi";
import { PiHarness } from "agents/harness/pi";
import { Lifecycle } from "agents/lifecycle";
import { createAI } from "agents/models/pi-ai";
import { z } from "zod";
// Your own code: reach a port on your sandbox and return its Response.
import { fetchSandboxPort } from "./hosts";
// Browser Run calls back into your Worker through this entrypoint.
export { InternalHostRouter } from "agents/browser";
export class BrowserAssistant extends DurableObject {
ai = createAI({ binding: this.env.AI });
registry = createRegistry();
browser = new Browser({
provider: browserRun(this.env.BROWSER, {
internalHosts: {
router: this.ctx.exports.InternalHostRouter,
hosts: {
"preview.internal": {
description: "Your dev servers",
// What the model passes to browser.assignHost() to choose
// what this host serves. fetch receives it as its second argument.
target: z.object({ port: z.number().int().min(1024) }),
fetch: (request, { port }) => fetchSandboxPort(port, request),
},
},
},
}),
});
harness = new PiHarness({
harness: ({ storage, context, onWake }) => {
const models = createModels();
models.setProvider(this.ai.provider);
this.registry.install({
name: "browser",
tools: [
browserTool({ browser: this.browser, loader: this.env.LOADER }),
],
});
return Harness.open(
storage,
{ models, registry: this.registry, onWake },
context,
);
},
defaults: { model: this.ai("@cf/zai-org/glm-5.3-flash") },
});
lifecycle = Lifecycle.install(this).use(this.browser).use(this.harness);
// InternalHostRouter calls this method to reach your Browser.
fetchInternalHost(message) {
return this.browser.fetchInternalHost(message);
}
async ask(prompt) {
const { text } = await this.harness.prompt(prompt);
return text;
}
}
```
```
import { DurableObject } from "cloudflare:workers";
import { createModels } from "@earendil-works/pi-ai/models";
import { createRegistry, Harness } from "@earendil-works/pi-durable";
import { Browser, browserRun, type InternalHostMessage } from "agents/browser";
import { browserTool } from "agents/browser/pi";
import { PiHarness } from "agents/harness/pi";
import { Lifecycle } from "agents/lifecycle";
import { createAI } from "agents/models/pi-ai";
import { z } from "zod";
// Your own code: reach a port on your sandbox and return its Response.
import { fetchSandboxPort } from "./hosts";
// Browser Run calls back into your Worker through this entrypoint.
export { InternalHostRouter } from "agents/browser";
export class BrowserAssistant extends DurableObject {
ai = createAI({ binding: this.env.AI });
registry = createRegistry();
browser = new Browser({
provider: browserRun(this.env.BROWSER, {
internalHosts: {
router: this.ctx.exports.InternalHostRouter,
hosts: {
"preview.internal": {
description: "Your dev servers",
// What the model passes to browser.assignHost() to choose
// what this host serves. fetch receives it as its second argument.
target: z.object({ port: z.number().int().min(1024) }),
fetch: (request, { port }) => fetchSandboxPort(port, request),
},
},
},
}),
});
harness = new PiHarness({
harness: ({ storage, context, onWake }) => {
const models = createModels();
models.setProvider(this.ai.provider);
this.registry.install({
name: "browser",
tools: [
browserTool({ browser: this.browser, loader: this.env.LOADER }),
],
});
return Harness.open(
storage,
{ models, registry: this.registry, onWake },
context,
);
},
defaults: { model: this.ai("@cf/zai-org/glm-5.3-flash") },
});
lifecycle = Lifecycle.install(this).use(this.browser).use(this.harness);
// InternalHostRouter calls this method to reach your Browser.
fetchInternalHost(message: InternalHostMessage) {
return this.browser.fetchInternalHost(message);
}
async ask(prompt: string) {
const { text } = await this.harness.prompt(prompt);
return text;
}
}
```
To use another harness, keep the browser configuration. Choose a [browser-tool adapter](https://developers.cloudflare.com/agents/tools/browser/#use-another-harness) if one is available, or implement a tool using `browser.session()`.
`browserTool` tells the model about each host, including the schema of its `target`. Because `preview.internal` has a `target` schema, the model can choose which port it serves. To check its dev server, the model calls `browser.assignHost()` with a target that matches the schema, such as `{ port: 5173 }`, and opens the URL it gets back:
```
const { url } = await browser.assignHost({
host: "preview.internal",
target: { port: 5173 },
});
await browser.cdp({
method: "Page.navigate",
params: { url },
sessionId: "active",
});
```
The target is checked against the schema and stored, so it lasts across turns, browser restarts, and redeploys. Each request to `http://preview.internal/` then calls the host's `fetch` function with `{ port: 5173 }`.
This release also simplifies `browserTool`. It runs the model's code in its own [Dynamic Worker](https://developers.cloudflare.com/dynamic-workers/) and no longer needs a `CodemodeRuntime` export. Refer to the [v0.31.0 upgrade notes ↗︎](https://github.com/cloudflare/agents/releases/tag/agents%400.31.0) if you already use `browserTool`.
For the complete example, limits, and security guidance, refer to [Internal hostnames](https://developers.cloudflare.com/agents/tools/browser/#internal-hostnames).