From e5273fcedd8294270dc2420749f569a17fd61d4c Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 27 Mar 2026 15:22:14 +0000 Subject: [PATCH 01/29] [wrangler] Add wrangler agent-memory namespace commands --- .changeset/agent-memory-namespace-commands.md | 14 + packages/wrangler/e2e/agent-memory.test.ts | 77 +++++ .../src/__tests__/agent-memory.test.ts | 284 ++++++++++++++++++ packages/wrangler/src/__tests__/index.test.ts | 2 + packages/wrangler/src/agent-memory/client.ts | 60 ++++ packages/wrangler/src/agent-memory/create.ts | 26 ++ packages/wrangler/src/agent-memory/delete.ts | 40 +++ packages/wrangler/src/agent-memory/get.ts | 45 +++ packages/wrangler/src/agent-memory/index.ts | 18 ++ packages/wrangler/src/agent-memory/list.ts | 48 +++ packages/wrangler/src/core/teams.d.ts | 1 + packages/wrangler/src/index.ts | 34 +++ tools/e2e/common.ts | 63 ++++ tools/e2e/e2eCleanup.ts | 19 ++ 14 files changed, 731 insertions(+) create mode 100644 .changeset/agent-memory-namespace-commands.md create mode 100644 packages/wrangler/e2e/agent-memory.test.ts create mode 100644 packages/wrangler/src/__tests__/agent-memory.test.ts create mode 100644 packages/wrangler/src/agent-memory/client.ts create mode 100644 packages/wrangler/src/agent-memory/create.ts create mode 100644 packages/wrangler/src/agent-memory/delete.ts create mode 100644 packages/wrangler/src/agent-memory/get.ts create mode 100644 packages/wrangler/src/agent-memory/index.ts create mode 100644 packages/wrangler/src/agent-memory/list.ts diff --git a/.changeset/agent-memory-namespace-commands.md b/.changeset/agent-memory-namespace-commands.md new file mode 100644 index 0000000000..0e0aca1a06 --- /dev/null +++ b/.changeset/agent-memory-namespace-commands.md @@ -0,0 +1,14 @@ +--- +"wrangler": minor +--- + +Add `wrangler agent-memory namespace` commands + +The following commands have been added for managing Agent Memory namespaces: + +```bash +wrangler agent-memory namespace create +wrangler agent-memory namespace list [--json] +wrangler agent-memory namespace get [--json] +wrangler agent-memory namespace delete [--force] +``` diff --git a/packages/wrangler/e2e/agent-memory.test.ts b/packages/wrangler/e2e/agent-memory.test.ts new file mode 100644 index 0000000000..a9f609330b --- /dev/null +++ b/packages/wrangler/e2e/agent-memory.test.ts @@ -0,0 +1,77 @@ +import { describe, it } from "vitest"; +import { CLOUDFLARE_ACCOUNT_ID } from "./helpers/account-id"; +import { WranglerE2ETestHelper } from "./helpers/e2e-wrangler-test"; +import { generateResourceName } from "./helpers/generate-resource-name"; +import { normalizeOutput } from "./helpers/normalize"; + +describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)("agent-memory", () => { + const namespaceName = generateResourceName("agent-memory"); + let namespaceId = ""; + + const helper = new WranglerE2ETestHelper(); + + const normalize = (str: string) => + normalizeOutput(str, { [namespaceName]: "tmp-e2e-agent-memory" }); + + it("create namespace", async ({ expect }) => { + const output = await helper.run( + `wrangler agent-memory namespace create ${namespaceName}` + ); + + expect(normalize(output.stdout)).toContain( + "✅ Created Agent Memory namespace" + ); + expect(normalize(output.stdout)).toContain(`Name: tmp-e2e-agent-memory`); + expect(output.stderr).toBe(""); + + // Extract the namespace ID for use in subsequent tests + const match = output.stdout.match(/ID:\s+(\S+)/); + if (!match) { + throw new Error("Could not extract namespace ID from create output"); + } + namespaceId = match[1]; + }); + + it("list namespaces", async ({ expect }) => { + const output = await helper.run(`wrangler agent-memory namespace list`); + + expect(normalize(output.stdout)).toContain("tmp-e2e-agent-memory"); + expect(output.stderr).toBe(""); + }); + + it("list namespaces --json", async ({ expect }) => { + const output = await helper.run( + `wrangler agent-memory namespace list --json` + ); + + const parsed = JSON.parse(output.stdout) as Array<{ + id: string; + name: string; + }>; + const found = parsed.find((ns) => ns.name === namespaceName); + expect(found).toBeDefined(); + expect(found?.id).toBe(namespaceId); + expect(output.stderr).toBe(""); + }); + + it("get namespace", async ({ expect }) => { + const output = await helper.run( + `wrangler agent-memory namespace get ${namespaceId}` + ); + + expect(normalize(output.stdout)).toContain("tmp-e2e-agent-memory"); + expect(output.stdout).toContain(namespaceId); + expect(output.stderr).toBe(""); + }); + + it("delete namespace", async ({ expect }) => { + const output = await helper.run( + `wrangler agent-memory namespace delete ${namespaceId} --force` + ); + + expect(output.stdout).toContain( + `✅ Deleted Agent Memory namespace ${namespaceId}` + ); + expect(output.stderr).toBe(""); + }); +}); diff --git a/packages/wrangler/src/__tests__/agent-memory.test.ts b/packages/wrangler/src/__tests__/agent-memory.test.ts new file mode 100644 index 0000000000..c6aecdd265 --- /dev/null +++ b/packages/wrangler/src/__tests__/agent-memory.test.ts @@ -0,0 +1,284 @@ +import { http, HttpResponse } from "msw"; +import { afterEach, describe, it } from "vitest"; +import { endEventLoop } from "./helpers/end-event-loop"; +import { mockAccountId, mockApiToken } from "./helpers/mock-account-id"; +import { mockConsoleMethods } from "./helpers/mock-console"; +import { clearDialogs, mockConfirm } from "./helpers/mock-dialogs"; +import { useMockIsTTY } from "./helpers/mock-istty"; +import { createFetchResult, msw } from "./helpers/msw"; +import { runInTempDir } from "./helpers/run-in-tmp"; +import { runWrangler } from "./helpers/run-wrangler"; +import type { AgentMemoryNamespace } from "../agent-memory/client"; + +const TEST_NAMESPACE: AgentMemoryNamespace = { + id: "01HNXYZ1234567890ABCDEFGH", + name: "my-namespace", + account_id: "some-account-id", + created_at: "2024-01-01T00:00:00Z", + updated_at: "2024-01-02T00:00:00Z", +}; + +describe("agent-memory help", () => { + const std = mockConsoleMethods(); + runInTempDir(); + + it("should show help text when no arguments are passed", async ({ + expect, + }) => { + await runWrangler("agent-memory"); + await endEventLoop(); + + expect(std.err).toMatchInlineSnapshot(`""`); + expect(std.out).toMatchInlineSnapshot(` + "wrangler agent-memory + + 🧠 Manage Agent Memory namespaces [open beta] + + COMMANDS + wrangler agent-memory namespace Manage Agent Memory namespaces [open beta] + + GLOBAL FLAGS + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" + `); + }); + + it("should show help text for the namespace subcommand", async ({ + expect, + }) => { + await runWrangler("agent-memory namespace"); + await endEventLoop(); + + expect(std.err).toMatchInlineSnapshot(`""`); + expect(std.out).toMatchInlineSnapshot(` + "wrangler agent-memory namespace + + Manage Agent Memory namespaces [open beta] + + COMMANDS + wrangler agent-memory namespace create Create a new Agent Memory namespace [open beta] + wrangler agent-memory namespace list List all Agent Memory namespaces associated with your account [open beta] + wrangler agent-memory namespace get Get details for a given Agent Memory namespace [open beta] + wrangler agent-memory namespace delete Delete a given Agent Memory namespace [open beta] + + GLOBAL FLAGS + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" + `); + }); +}); + +describe("agent-memory namespace commands", () => { + mockAccountId(); + mockApiToken(); + runInTempDir(); + const { setIsTTY } = useMockIsTTY(); + const std = mockConsoleMethods(); + + afterEach(() => { + clearDialogs(); + }); + + // ── create ──────────────────────────────────────────────────────────────── + + it("should create a namespace", async ({ expect }) => { + msw.use( + http.post( + "*/accounts/:accountId/agentmemory/namespaces", + async ({ request }) => { + const body = (await request.json()) as { name: string }; + expect(body.name).toBe("my-namespace"); + return HttpResponse.json(createFetchResult(TEST_NAMESPACE, true)); + }, + { once: true } + ) + ); + + await runWrangler("agent-memory namespace create my-namespace"); + + expect(std.out).toContain("✅ Created Agent Memory namespace"); + expect(std.out).toContain(TEST_NAMESPACE.id); + expect(std.out).toContain(TEST_NAMESPACE.name); + expect(std.err).toMatchInlineSnapshot(`""`); + }); + + // ── list ────────────────────────────────────────────────────────────────── + + it("should list namespaces in a table", async ({ expect }) => { + msw.use( + http.get( + "*/accounts/:accountId/agentmemory/namespaces", + () => { + return HttpResponse.json( + createFetchResult([TEST_NAMESPACE], true, [], [], { + cursor: "", + per_page: 20, + total_count: 1, + }) + ); + }, + { once: true } + ) + ); + + await runWrangler("agent-memory namespace list"); + + expect(std.out).toContain(TEST_NAMESPACE.id); + expect(std.out).toContain(TEST_NAMESPACE.name); + expect(std.err).toMatchInlineSnapshot(`""`); + }); + + it("should list namespaces as JSON with --json", async ({ expect }) => { + msw.use( + http.get( + "*/accounts/:accountId/agentmemory/namespaces", + () => { + return HttpResponse.json( + createFetchResult([TEST_NAMESPACE], true, [], [], { + cursor: "", + per_page: 20, + total_count: 1, + }) + ); + }, + { once: true } + ) + ); + + await runWrangler("agent-memory namespace list --json"); + + const parsed = JSON.parse(std.out); + expect(parsed).toEqual([TEST_NAMESPACE]); + expect(std.err).toMatchInlineSnapshot(`""`); + }); + + it("should print a message when no namespaces exist", async ({ expect }) => { + msw.use( + http.get( + "*/accounts/:accountId/agentmemory/namespaces", + () => { + return HttpResponse.json( + createFetchResult([], true, [], [], { + cursor: "", + per_page: 20, + total_count: 0, + }) + ); + }, + { once: true } + ) + ); + + await runWrangler("agent-memory namespace list"); + + expect(std.out).toContain("No Agent Memory namespaces found"); + expect(std.err).toMatchInlineSnapshot(`""`); + }); + + // ── get ─────────────────────────────────────────────────────────────────── + + it("should get a namespace in a table", async ({ expect }) => { + msw.use( + http.get( + `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.id}`, + () => { + return HttpResponse.json(createFetchResult(TEST_NAMESPACE, true)); + }, + { once: true } + ) + ); + + await runWrangler(`agent-memory namespace get ${TEST_NAMESPACE.id}`); + + expect(std.out).toContain(TEST_NAMESPACE.id); + expect(std.out).toContain(TEST_NAMESPACE.name); + expect(std.err).toMatchInlineSnapshot(`""`); + }); + + it("should get a namespace as JSON with --json", async ({ expect }) => { + msw.use( + http.get( + `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.id}`, + () => { + return HttpResponse.json(createFetchResult(TEST_NAMESPACE, true)); + }, + { once: true } + ) + ); + + await runWrangler(`agent-memory namespace get ${TEST_NAMESPACE.id} --json`); + + const parsed = JSON.parse(std.out); + expect(parsed).toEqual(TEST_NAMESPACE); + expect(std.err).toMatchInlineSnapshot(`""`); + }); + + // ── delete ──────────────────────────────────────────────────────────────── + + it("should delete a namespace after confirmation", async ({ expect }) => { + setIsTTY(true); + mockConfirm({ + text: `OK to delete the namespace '${TEST_NAMESPACE.id}'?`, + result: true, + }); + + msw.use( + http.delete( + `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.id}`, + () => { + return HttpResponse.json(createFetchResult(null, true)); + }, + { once: true } + ) + ); + + await runWrangler(`agent-memory namespace delete ${TEST_NAMESPACE.id}`); + + expect(std.out).toContain(`✅ Deleted Agent Memory namespace`); + expect(std.err).toMatchInlineSnapshot(`""`); + }); + + it("should cancel deletion when confirmation is declined", async ({ + expect, + }) => { + setIsTTY(true); + mockConfirm({ + text: `OK to delete the namespace '${TEST_NAMESPACE.id}'?`, + result: false, + }); + + await runWrangler(`agent-memory namespace delete ${TEST_NAMESPACE.id}`); + + expect(std.out).toContain("Deletion cancelled."); + expect(std.err).toMatchInlineSnapshot(`""`); + }); + + it("should delete a namespace without confirmation when --force is passed", async ({ + expect, + }) => { + msw.use( + http.delete( + `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.id}`, + () => { + return HttpResponse.json(createFetchResult(null, true)); + }, + { once: true } + ) + ); + + await runWrangler( + `agent-memory namespace delete ${TEST_NAMESPACE.id} --force` + ); + + expect(std.out).toContain(`✅ Deleted Agent Memory namespace`); + expect(std.err).toMatchInlineSnapshot(`""`); + }); +}); diff --git a/packages/wrangler/src/__tests__/index.test.ts b/packages/wrangler/src/__tests__/index.test.ts index 67a92345ec..1a767d005a 100644 --- a/packages/wrangler/src/__tests__/index.test.ts +++ b/packages/wrangler/src/__tests__/index.test.ts @@ -50,6 +50,7 @@ describe("wrangler", () => { wrangler whoami 🕵️ Retrieve your user information COMPUTE & AI + wrangler agent-memory 🧠 Manage Agent Memory namespaces [open beta] wrangler ai 🤖 Manage AI models wrangler ai-search 🔍 Manage AI Search instances [open beta] wrangler browser 🌐 Manage Browser Run sessions [open beta] @@ -128,6 +129,7 @@ describe("wrangler", () => { wrangler whoami 🕵️ Retrieve your user information COMPUTE & AI + wrangler agent-memory 🧠 Manage Agent Memory namespaces [open beta] wrangler ai 🤖 Manage AI models wrangler ai-search 🔍 Manage AI Search instances [open beta] wrangler browser 🌐 Manage Browser Run sessions [open beta] diff --git a/packages/wrangler/src/agent-memory/client.ts b/packages/wrangler/src/agent-memory/client.ts new file mode 100644 index 0000000000..e72623ddd5 --- /dev/null +++ b/packages/wrangler/src/agent-memory/client.ts @@ -0,0 +1,60 @@ +import { fetchListResult, fetchResult } from "../cfetch"; +import { requireAuth } from "../user"; +import type { Config } from "@cloudflare/workers-utils"; + +export type AgentMemoryNamespace = { + id: string; + name: string; + account_id: string; + created_at: string; + updated_at: string; +}; + +export async function createNamespace( + config: Config, + name: string +): Promise { + const accountId = await requireAuth(config); + return await fetchResult( + config, + `/accounts/${accountId}/agentmemory/namespaces`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name }), + } + ); +} + +export async function listNamespaces( + config: Config +): Promise { + const accountId = await requireAuth(config); + return await fetchListResult( + config, + `/accounts/${accountId}/agentmemory/namespaces` + ); +} + +export async function getNamespace( + config: Config, + namespaceId: string +): Promise { + const accountId = await requireAuth(config); + return await fetchResult( + config, + `/accounts/${accountId}/agentmemory/namespaces/${namespaceId}` + ); +} + +export async function deleteNamespace( + config: Config, + namespaceId: string +): Promise { + const accountId = await requireAuth(config); + await fetchResult( + config, + `/accounts/${accountId}/agentmemory/namespaces/${namespaceId}`, + { method: "DELETE" } + ); +} diff --git a/packages/wrangler/src/agent-memory/create.ts b/packages/wrangler/src/agent-memory/create.ts new file mode 100644 index 0000000000..0023fb21a9 --- /dev/null +++ b/packages/wrangler/src/agent-memory/create.ts @@ -0,0 +1,26 @@ +import { createCommand } from "../core/create-command"; +import { logger } from "../logger"; +import { createNamespace } from "./client"; + +export const agentMemoryNamespaceCreateCommand = createCommand({ + metadata: { + description: "Create a new Agent Memory namespace", + status: "open beta", + owner: "Product: Agent Memory", + }, + args: { + namespace: { + type: "string", + demandOption: true, + description: + "The name for the new namespace (max 512 characters, no control characters)", + }, + }, + positionalArgs: ["namespace"], + async handler({ namespace }, { config }) { + const result = await createNamespace(config, namespace); + logger.log(`✅ Created Agent Memory namespace`); + logger.log(` ID: ${result.id}`); + logger.log(` Name: ${result.name}`); + }, +}); diff --git a/packages/wrangler/src/agent-memory/delete.ts b/packages/wrangler/src/agent-memory/delete.ts new file mode 100644 index 0000000000..47e23a1163 --- /dev/null +++ b/packages/wrangler/src/agent-memory/delete.ts @@ -0,0 +1,40 @@ +import { createCommand } from "../core/create-command"; +import { confirm } from "../dialogs"; +import { logger } from "../logger"; +import { deleteNamespace } from "./client"; + +export const agentMemoryNamespaceDeleteCommand = createCommand({ + metadata: { + description: "Delete a given Agent Memory namespace", + status: "open beta", + owner: "Product: Agent Memory", + }, + args: { + namespace_id: { + type: "string", + demandOption: true, + description: "The ID of the namespace to delete", + }, + force: { + type: "boolean", + alias: "y", + default: false, + description: "Skip confirmation", + }, + }, + positionalArgs: ["namespace_id"], + async handler({ namespace_id, force }, { config }) { + if (!force) { + const confirmedDeletion = await confirm( + `OK to delete the namespace '${namespace_id}'?` + ); + if (!confirmedDeletion) { + logger.log("Deletion cancelled."); + return; + } + } + + await deleteNamespace(config, namespace_id); + logger.log(`✅ Deleted Agent Memory namespace ${namespace_id}`); + }, +}); diff --git a/packages/wrangler/src/agent-memory/get.ts b/packages/wrangler/src/agent-memory/get.ts new file mode 100644 index 0000000000..dac39380c3 --- /dev/null +++ b/packages/wrangler/src/agent-memory/get.ts @@ -0,0 +1,45 @@ +import { createCommand } from "../core/create-command"; +import { logger } from "../logger"; +import { getNamespace } from "./client"; + +export const agentMemoryNamespaceGetCommand = createCommand({ + metadata: { + description: "Get details for a given Agent Memory namespace", + status: "open beta", + owner: "Product: Agent Memory", + }, + behaviour: { + printBanner: (args) => !args.json, + }, + args: { + namespace_id: { + type: "string", + demandOption: true, + description: "The ID of the namespace to retrieve", + }, + json: { + type: "boolean", + default: false, + description: "Return output as JSON", + }, + }, + positionalArgs: ["namespace_id"], + async handler({ namespace_id, json }, { config }) { + const ns = await getNamespace(config, namespace_id); + + if (json) { + logger.json(ns); + return; + } + + logger.table([ + { + namespace_id: ns.id, + name: ns.name, + account_id: ns.account_id, + created_at: ns.created_at, + updated_at: ns.updated_at, + }, + ]); + }, +}); diff --git a/packages/wrangler/src/agent-memory/index.ts b/packages/wrangler/src/agent-memory/index.ts new file mode 100644 index 0000000000..ff93200a71 --- /dev/null +++ b/packages/wrangler/src/agent-memory/index.ts @@ -0,0 +1,18 @@ +import { createNamespace } from "../core/create-command"; + +export const agentMemoryNamespace = createNamespace({ + metadata: { + description: "🧠 Manage Agent Memory namespaces", + status: "open beta", + owner: "Product: Agent Memory", + category: "Compute & AI", + }, +}); + +export const agentMemoryNamespaceNamespace = createNamespace({ + metadata: { + description: "Manage Agent Memory namespaces", + status: "open beta", + owner: "Product: Agent Memory", + }, +}); diff --git a/packages/wrangler/src/agent-memory/list.ts b/packages/wrangler/src/agent-memory/list.ts new file mode 100644 index 0000000000..7c8d7c358c --- /dev/null +++ b/packages/wrangler/src/agent-memory/list.ts @@ -0,0 +1,48 @@ +import { createCommand } from "../core/create-command"; +import { logger } from "../logger"; +import { listNamespaces } from "./client"; + +export const agentMemoryNamespaceListCommand = createCommand({ + metadata: { + description: + "List all Agent Memory namespaces associated with your account", + status: "open beta", + owner: "Product: Agent Memory", + }, + behaviour: { + printBanner: (args) => !args.json, + }, + args: { + json: { + type: "boolean", + default: false, + description: "Return output as JSON", + }, + }, + async handler({ json }, { config }) { + if (!json) { + logger.log(`📋 Listing Agent Memory namespaces...`); + } + const namespaces = await listNamespaces(config); + + if (json) { + logger.json(namespaces); + return; + } + + if (namespaces.length === 0) { + logger.log( + `No Agent Memory namespaces found. Use 'wrangler agent-memory namespace create ' to create one.` + ); + return; + } + + logger.table( + namespaces.map((ns) => ({ + namespace_id: ns.id, + name: ns.name, + created_at: ns.created_at, + })) + ); + }, +}); diff --git a/packages/wrangler/src/core/teams.d.ts b/packages/wrangler/src/core/teams.d.ts index 35874d2ce4..00f5ca9dfd 100644 --- a/packages/wrangler/src/core/teams.d.ts +++ b/packages/wrangler/src/core/teams.d.ts @@ -15,6 +15,7 @@ export type Teams = | "Product: Queues" | "Product: AI" | "Product: AI Search" + | "Product: Agent Memory" | "Product: Hyperdrive" | "Product: Pipelines" | "Product: Vectorize" diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index 5fcf762300..8484793424 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -9,6 +9,14 @@ import chalk from "chalk"; import { EnvHttpProxyAgent, setGlobalDispatcher } from "undici"; import makeCLI from "yargs"; import { version as wranglerVersion } from "../package.json"; +import { + agentMemoryNamespace, + agentMemoryNamespaceNamespace, +} from "./agent-memory"; +import { agentMemoryNamespaceCreateCommand } from "./agent-memory/create"; +import { agentMemoryNamespaceDeleteCommand } from "./agent-memory/delete"; +import { agentMemoryNamespaceGetCommand } from "./agent-memory/get"; +import { agentMemoryNamespaceListCommand } from "./agent-memory/list"; import { aiFineTuneNamespace, aiNamespace } from "./ai"; import { aiSearchCreateCommand } from "./ai-search/create"; import { aiSearchDeleteCommand } from "./ai-search/delete"; @@ -1860,6 +1868,32 @@ export function createCLIParser(argv: string[]) { ]); registry.registerNamespace("browser"); + // agent-memory + registry.define([ + { command: "wrangler agent-memory", definition: agentMemoryNamespace }, + { + command: "wrangler agent-memory namespace", + definition: agentMemoryNamespaceNamespace, + }, + { + command: "wrangler agent-memory namespace create", + definition: agentMemoryNamespaceCreateCommand, + }, + { + command: "wrangler agent-memory namespace list", + definition: agentMemoryNamespaceListCommand, + }, + { + command: "wrangler agent-memory namespace get", + definition: agentMemoryNamespaceGetCommand, + }, + { + command: "wrangler agent-memory namespace delete", + definition: agentMemoryNamespaceDeleteCommand, + }, + ]); + registry.registerNamespace("agent-memory"); + // secrets store registry.define([ { command: "wrangler secrets-store", definition: secretsStoreNamespace }, diff --git a/tools/e2e/common.ts b/tools/e2e/common.ts index 37b685c33c..eca637ed0d 100644 --- a/tools/e2e/common.ts +++ b/tools/e2e/common.ts @@ -49,6 +49,14 @@ export type HyperdriveConfig = { created_on: string; }; +export type AgentMemoryNamespace = { + id: string; + name: string; + account_id: string; + created_at: string; + updated_at: string; +}; + export type MTlsCertificateResponse = { id: string; name?: string; @@ -333,6 +341,61 @@ export const deleteCertificate = async (id: string) => { return await apiFetch(`/mtls_certificates/${id}`, "DELETE"); }; +export const listTmpAgentMemoryNamespaces = async () => { + // The Agent Memory API uses cursor-based pagination, so we follow cursors manually + // rather than using apiFetchList (which uses page-number pagination). + const baseUrl = `https://api.cloudflare.com/client/v4/accounts/${process.env.CLOUDFLARE_ACCOUNT_ID}`; + const results: AgentMemoryNamespace[] = []; + let cursor: string | undefined; + + while (true) { + const queryString = cursor + ? "?" + new URLSearchParams({ cursor }).toString() + : ""; + const url = `${baseUrl}/agentmemory/namespaces${queryString}`; + + const response = await fetch(url, { + method: "GET", + headers: { + Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`, + }, + }); + + if (!response.ok) { + console.error( + "Failed to list Agent Memory namespaces", + response.status, + await response.text() + ); + return []; + } + + const json = (await response.json()) as { + result: AgentMemoryNamespace[]; + result_info?: { cursor?: string }; + }; + + results.push(...json.result); + + const nextCursor = json.result_info?.cursor; + if (!nextCursor) { + break; + } + cursor = nextCursor; + } + + return results.filter( + (ns) => + ns.name.startsWith("tmp-e2e-") && + // Namespaces are more than an hour old + Date.now() - new Date(ns.created_at).valueOf() > 1000 * 60 * 60 + ); +}; + +export const deleteAgentMemoryNamespace = async (id: string) => { + return await apiFetch(`/agentmemory/namespaces/${id}`, "DELETE"); +}; + // Note: the container images functions below don't directly use the REST API since // they interact with the cloudflare images registry which has it's own // non-trivial auth mechanism, so instead of duplicating a bunch of logic diff --git a/tools/e2e/e2eCleanup.ts b/tools/e2e/e2eCleanup.ts index f6f28c0283..8e53035e0e 100644 --- a/tools/e2e/e2eCleanup.ts +++ b/tools/e2e/e2eCleanup.ts @@ -1,4 +1,5 @@ import { + deleteAgentMemoryNamespace, deleteCertificate, deleteContainerApplication, deleteContainerImage, @@ -11,6 +12,7 @@ import { listCertificates, listE2eContainerImages, listHyperdriveConfigs, + listTmpAgentMemoryNamespaces, listTmpDatabases, listTmpE2EContainerApplications, listTmpE2EProjects, @@ -54,6 +56,8 @@ async function run() { await deleteContainerApplications(); + await deleteAgentMemoryNamespaces(); + deleteContainerImages(); } @@ -171,6 +175,21 @@ async function deleteMtlsCertificates() { } } +async function deleteAgentMemoryNamespaces() { + const namespacesToDelete = await listTmpAgentMemoryNamespaces(); + for (const ns of namespacesToDelete) { + console.log("Deleting Agent Memory namespace: " + ns.name); + if (await deleteAgentMemoryNamespace(ns.id)) { + console.log(`Successfully deleted Agent Memory namespace ${ns.id}`); + } else { + console.log(`Failed to delete Agent Memory namespace ${ns.id}`); + } + } + if (namespacesToDelete.length === 0) { + console.log(`No Agent Memory namespaces to delete.`); + } +} + async function deleteContainerApplications() { const containers = await listTmpE2EContainerApplications(); for (const container of containers) { From 862b5d15bafc9873aa37ccf2f30ffc30af54eaa4 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 31 Mar 2026 11:26:25 +0100 Subject: [PATCH 02/29] [wrangler] Use namespace name instead of ID for agent-memory get/delete commands The Agent Memory API now uses namespace names in URL paths instead of IDs. Update get and delete commands to accept instead of , and fix create description to reflect the actual validation constraints (max 32 chars, alphanumeric with embedded hyphens). --- .changeset/agent-memory-namespace-commands.md | 4 +-- packages/wrangler/e2e/agent-memory.test.ts | 8 ++--- .../src/__tests__/agent-memory.test.ts | 32 ++++++++++--------- packages/wrangler/src/agent-memory/client.ts | 8 ++--- packages/wrangler/src/agent-memory/create.ts | 2 +- packages/wrangler/src/agent-memory/delete.ts | 14 ++++---- packages/wrangler/src/agent-memory/get.ts | 10 +++--- tools/e2e/common.ts | 4 +-- tools/e2e/e2eCleanup.ts | 6 ++-- 9 files changed, 45 insertions(+), 43 deletions(-) diff --git a/.changeset/agent-memory-namespace-commands.md b/.changeset/agent-memory-namespace-commands.md index 0e0aca1a06..57e1b72d42 100644 --- a/.changeset/agent-memory-namespace-commands.md +++ b/.changeset/agent-memory-namespace-commands.md @@ -9,6 +9,6 @@ The following commands have been added for managing Agent Memory namespaces: ```bash wrangler agent-memory namespace create wrangler agent-memory namespace list [--json] -wrangler agent-memory namespace get [--json] -wrangler agent-memory namespace delete [--force] +wrangler agent-memory namespace get [--json] +wrangler agent-memory namespace delete [--force] ``` diff --git a/packages/wrangler/e2e/agent-memory.test.ts b/packages/wrangler/e2e/agent-memory.test.ts index a9f609330b..b8c72d7824 100644 --- a/packages/wrangler/e2e/agent-memory.test.ts +++ b/packages/wrangler/e2e/agent-memory.test.ts @@ -56,7 +56,7 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)("agent-memory", () => { it("get namespace", async ({ expect }) => { const output = await helper.run( - `wrangler agent-memory namespace get ${namespaceId}` + `wrangler agent-memory namespace get ${namespaceName}` ); expect(normalize(output.stdout)).toContain("tmp-e2e-agent-memory"); @@ -66,11 +66,11 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)("agent-memory", () => { it("delete namespace", async ({ expect }) => { const output = await helper.run( - `wrangler agent-memory namespace delete ${namespaceId} --force` + `wrangler agent-memory namespace delete ${namespaceName} --force` ); - expect(output.stdout).toContain( - `✅ Deleted Agent Memory namespace ${namespaceId}` + expect(normalize(output.stdout)).toContain( + `✅ Deleted Agent Memory namespace tmp-e2e-agent-memory` ); expect(output.stderr).toBe(""); }); diff --git a/packages/wrangler/src/__tests__/agent-memory.test.ts b/packages/wrangler/src/__tests__/agent-memory.test.ts index c6aecdd265..20ddee22da 100644 --- a/packages/wrangler/src/__tests__/agent-memory.test.ts +++ b/packages/wrangler/src/__tests__/agent-memory.test.ts @@ -60,10 +60,10 @@ describe("agent-memory help", () => { Manage Agent Memory namespaces [open beta] COMMANDS - wrangler agent-memory namespace create Create a new Agent Memory namespace [open beta] - wrangler agent-memory namespace list List all Agent Memory namespaces associated with your account [open beta] - wrangler agent-memory namespace get Get details for a given Agent Memory namespace [open beta] - wrangler agent-memory namespace delete Delete a given Agent Memory namespace [open beta] + wrangler agent-memory namespace create Create a new Agent Memory namespace [open beta] + wrangler agent-memory namespace list List all Agent Memory namespaces associated with your account [open beta] + wrangler agent-memory namespace get Get details for a given Agent Memory namespace [open beta] + wrangler agent-memory namespace delete Delete a given Agent Memory namespace [open beta] GLOBAL FLAGS -c, --config Path to Wrangler configuration file [string] @@ -188,7 +188,7 @@ describe("agent-memory namespace commands", () => { it("should get a namespace in a table", async ({ expect }) => { msw.use( http.get( - `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.id}`, + `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.name}`, () => { return HttpResponse.json(createFetchResult(TEST_NAMESPACE, true)); }, @@ -196,7 +196,7 @@ describe("agent-memory namespace commands", () => { ) ); - await runWrangler(`agent-memory namespace get ${TEST_NAMESPACE.id}`); + await runWrangler(`agent-memory namespace get ${TEST_NAMESPACE.name}`); expect(std.out).toContain(TEST_NAMESPACE.id); expect(std.out).toContain(TEST_NAMESPACE.name); @@ -206,7 +206,7 @@ describe("agent-memory namespace commands", () => { it("should get a namespace as JSON with --json", async ({ expect }) => { msw.use( http.get( - `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.id}`, + `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.name}`, () => { return HttpResponse.json(createFetchResult(TEST_NAMESPACE, true)); }, @@ -214,7 +214,9 @@ describe("agent-memory namespace commands", () => { ) ); - await runWrangler(`agent-memory namespace get ${TEST_NAMESPACE.id} --json`); + await runWrangler( + `agent-memory namespace get ${TEST_NAMESPACE.name} --json` + ); const parsed = JSON.parse(std.out); expect(parsed).toEqual(TEST_NAMESPACE); @@ -226,13 +228,13 @@ describe("agent-memory namespace commands", () => { it("should delete a namespace after confirmation", async ({ expect }) => { setIsTTY(true); mockConfirm({ - text: `OK to delete the namespace '${TEST_NAMESPACE.id}'?`, + text: `OK to delete the namespace '${TEST_NAMESPACE.name}'?`, result: true, }); msw.use( http.delete( - `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.id}`, + `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.name}`, () => { return HttpResponse.json(createFetchResult(null, true)); }, @@ -240,7 +242,7 @@ describe("agent-memory namespace commands", () => { ) ); - await runWrangler(`agent-memory namespace delete ${TEST_NAMESPACE.id}`); + await runWrangler(`agent-memory namespace delete ${TEST_NAMESPACE.name}`); expect(std.out).toContain(`✅ Deleted Agent Memory namespace`); expect(std.err).toMatchInlineSnapshot(`""`); @@ -251,11 +253,11 @@ describe("agent-memory namespace commands", () => { }) => { setIsTTY(true); mockConfirm({ - text: `OK to delete the namespace '${TEST_NAMESPACE.id}'?`, + text: `OK to delete the namespace '${TEST_NAMESPACE.name}'?`, result: false, }); - await runWrangler(`agent-memory namespace delete ${TEST_NAMESPACE.id}`); + await runWrangler(`agent-memory namespace delete ${TEST_NAMESPACE.name}`); expect(std.out).toContain("Deletion cancelled."); expect(std.err).toMatchInlineSnapshot(`""`); @@ -266,7 +268,7 @@ describe("agent-memory namespace commands", () => { }) => { msw.use( http.delete( - `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.id}`, + `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.name}`, () => { return HttpResponse.json(createFetchResult(null, true)); }, @@ -275,7 +277,7 @@ describe("agent-memory namespace commands", () => { ); await runWrangler( - `agent-memory namespace delete ${TEST_NAMESPACE.id} --force` + `agent-memory namespace delete ${TEST_NAMESPACE.name} --force` ); expect(std.out).toContain(`✅ Deleted Agent Memory namespace`); diff --git a/packages/wrangler/src/agent-memory/client.ts b/packages/wrangler/src/agent-memory/client.ts index e72623ddd5..e03c099264 100644 --- a/packages/wrangler/src/agent-memory/client.ts +++ b/packages/wrangler/src/agent-memory/client.ts @@ -38,23 +38,23 @@ export async function listNamespaces( export async function getNamespace( config: Config, - namespaceId: string + namespaceName: string ): Promise { const accountId = await requireAuth(config); return await fetchResult( config, - `/accounts/${accountId}/agentmemory/namespaces/${namespaceId}` + `/accounts/${accountId}/agentmemory/namespaces/${namespaceName}` ); } export async function deleteNamespace( config: Config, - namespaceId: string + namespaceName: string ): Promise { const accountId = await requireAuth(config); await fetchResult( config, - `/accounts/${accountId}/agentmemory/namespaces/${namespaceId}`, + `/accounts/${accountId}/agentmemory/namespaces/${namespaceName}`, { method: "DELETE" } ); } diff --git a/packages/wrangler/src/agent-memory/create.ts b/packages/wrangler/src/agent-memory/create.ts index 0023fb21a9..d4cca8d1bd 100644 --- a/packages/wrangler/src/agent-memory/create.ts +++ b/packages/wrangler/src/agent-memory/create.ts @@ -13,7 +13,7 @@ export const agentMemoryNamespaceCreateCommand = createCommand({ type: "string", demandOption: true, description: - "The name for the new namespace (max 512 characters, no control characters)", + "The name for the new namespace (max 32 characters, alphanumeric with embedded hyphens)", }, }, positionalArgs: ["namespace"], diff --git a/packages/wrangler/src/agent-memory/delete.ts b/packages/wrangler/src/agent-memory/delete.ts index 47e23a1163..49d4ac543e 100644 --- a/packages/wrangler/src/agent-memory/delete.ts +++ b/packages/wrangler/src/agent-memory/delete.ts @@ -10,10 +10,10 @@ export const agentMemoryNamespaceDeleteCommand = createCommand({ owner: "Product: Agent Memory", }, args: { - namespace_id: { + namespace_name: { type: "string", demandOption: true, - description: "The ID of the namespace to delete", + description: "The name of the namespace to delete", }, force: { type: "boolean", @@ -22,11 +22,11 @@ export const agentMemoryNamespaceDeleteCommand = createCommand({ description: "Skip confirmation", }, }, - positionalArgs: ["namespace_id"], - async handler({ namespace_id, force }, { config }) { + positionalArgs: ["namespace_name"], + async handler({ namespace_name, force }, { config }) { if (!force) { const confirmedDeletion = await confirm( - `OK to delete the namespace '${namespace_id}'?` + `OK to delete the namespace '${namespace_name}'?` ); if (!confirmedDeletion) { logger.log("Deletion cancelled."); @@ -34,7 +34,7 @@ export const agentMemoryNamespaceDeleteCommand = createCommand({ } } - await deleteNamespace(config, namespace_id); - logger.log(`✅ Deleted Agent Memory namespace ${namespace_id}`); + await deleteNamespace(config, namespace_name); + logger.log(`✅ Deleted Agent Memory namespace ${namespace_name}`); }, }); diff --git a/packages/wrangler/src/agent-memory/get.ts b/packages/wrangler/src/agent-memory/get.ts index dac39380c3..e51fd66786 100644 --- a/packages/wrangler/src/agent-memory/get.ts +++ b/packages/wrangler/src/agent-memory/get.ts @@ -12,10 +12,10 @@ export const agentMemoryNamespaceGetCommand = createCommand({ printBanner: (args) => !args.json, }, args: { - namespace_id: { + namespace_name: { type: "string", demandOption: true, - description: "The ID of the namespace to retrieve", + description: "The name of the namespace to retrieve", }, json: { type: "boolean", @@ -23,9 +23,9 @@ export const agentMemoryNamespaceGetCommand = createCommand({ description: "Return output as JSON", }, }, - positionalArgs: ["namespace_id"], - async handler({ namespace_id, json }, { config }) { - const ns = await getNamespace(config, namespace_id); + positionalArgs: ["namespace_name"], + async handler({ namespace_name, json }, { config }) { + const ns = await getNamespace(config, namespace_name); if (json) { logger.json(ns); diff --git a/tools/e2e/common.ts b/tools/e2e/common.ts index eca637ed0d..85b5eb7a58 100644 --- a/tools/e2e/common.ts +++ b/tools/e2e/common.ts @@ -392,8 +392,8 @@ export const listTmpAgentMemoryNamespaces = async () => { ); }; -export const deleteAgentMemoryNamespace = async (id: string) => { - return await apiFetch(`/agentmemory/namespaces/${id}`, "DELETE"); +export const deleteAgentMemoryNamespace = async (name: string) => { + return await apiFetch(`/agentmemory/namespaces/${name}`, "DELETE"); }; // Note: the container images functions below don't directly use the REST API since diff --git a/tools/e2e/e2eCleanup.ts b/tools/e2e/e2eCleanup.ts index 8e53035e0e..bbeb434c4c 100644 --- a/tools/e2e/e2eCleanup.ts +++ b/tools/e2e/e2eCleanup.ts @@ -179,10 +179,10 @@ async function deleteAgentMemoryNamespaces() { const namespacesToDelete = await listTmpAgentMemoryNamespaces(); for (const ns of namespacesToDelete) { console.log("Deleting Agent Memory namespace: " + ns.name); - if (await deleteAgentMemoryNamespace(ns.id)) { - console.log(`Successfully deleted Agent Memory namespace ${ns.id}`); + if (await deleteAgentMemoryNamespace(ns.name)) { + console.log(`Successfully deleted Agent Memory namespace ${ns.name}`); } else { - console.log(`Failed to delete Agent Memory namespace ${ns.id}`); + console.log(`Failed to delete Agent Memory namespace ${ns.name}`); } } if (namespacesToDelete.length === 0) { From cb7f39bce9a54fc6da5a682eb758265e1091d308 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 1 Apr 2026 07:07:26 +0100 Subject: [PATCH 03/29] [workers-utils] Add agent_memory binding config and typing --- packages/workers-utils/src/config/config.ts | 1 + .../workers-utils/src/config/environment.ts | 18 +++++++ .../workers-utils/src/config/validation.ts | 48 +++++++++++++++++++ .../src/map-worker-metadata-bindings.ts | 9 ++++ packages/workers-utils/src/types.ts | 3 ++ packages/workers-utils/src/worker.ts | 6 +++ 6 files changed, 85 insertions(+) diff --git a/packages/workers-utils/src/config/config.ts b/packages/workers-utils/src/config/config.ts index 8402fd89aa..8167bf6dc8 100644 --- a/packages/workers-utils/src/config/config.ts +++ b/packages/workers-utils/src/config/config.ts @@ -336,6 +336,7 @@ export const defaultWranglerConfig: Config = { vectorize: [], ai_search_namespaces: [], ai_search: [], + agent_memory: [], hyperdrive: [], workflows: [], secrets_store_secrets: [], diff --git a/packages/workers-utils/src/config/environment.ts b/packages/workers-utils/src/config/environment.ts index f5577461aa..3196a5f572 100644 --- a/packages/workers-utils/src/config/environment.ts +++ b/packages/workers-utils/src/config/environment.ts @@ -1052,6 +1052,24 @@ export interface EnvironmentNonInheritable { remote?: boolean; }[]; + /** + * Specifies Agent Memory namespace bindings that are bound to this Worker environment. + * + * NOTE: This field is not automatically inherited from the top level environment, + * and so must be specified in every named environment. + * + * @default [] + * @nonInheritable + */ + agent_memory: { + /** The binding name used to refer to the Agent Memory namespace in the Worker. */ + binding: string; + /** The user-chosen namespace name. Must exist in Cloudflare at deploy time. */ + namespace: string; + /** Whether the Agent Memory binding should be remote in local development */ + remote?: boolean; + }[]; + /** * Specifies Hyperdrive configs that are bound to this Worker environment. * diff --git a/packages/workers-utils/src/config/validation.ts b/packages/workers-utils/src/config/validation.ts index 9774be5620..71246c2623 100644 --- a/packages/workers-utils/src/config/validation.ts +++ b/packages/workers-utils/src/config/validation.ts @@ -82,6 +82,7 @@ export type ConfigBindingFieldName = | "vectorize" | "ai_search_namespaces" | "ai_search" + | "agent_memory" | "hyperdrive" | "r2_buckets" | "logfwdr" @@ -124,6 +125,7 @@ export const friendlyBindingNames: Record = { vectorize: "Vectorize Index", ai_search_namespaces: "AI Search Namespace", ai_search: "AI Search Instance", + agent_memory: "Agent Memory", hyperdrive: "Hyperdrive Config", r2_buckets: "R2 Bucket", logfwdr: "logfwdr", @@ -181,6 +183,7 @@ const bindingTypeFriendlyNames: Record = { vectorize: "Vectorize Index", ai_search_namespace: "AI Search Namespace", ai_search: "AI Search Instance", + agent_memory: "Agent Memory", hyperdrive: "Hyperdrive Config", service: "Worker", fetcher: "Service Binding", @@ -1756,6 +1759,16 @@ function normalizeAndValidateEnvironment( validateBindingArray(envName, validateAISearchBinding), [] ), + agent_memory: notInheritable( + diagnostics, + topLevelEnv, + rawConfig, + rawEnv, + envName, + "agent_memory", + validateBindingArray(envName, validateAgentMemoryBinding), + [] + ), hyperdrive: notInheritable( diagnostics, topLevelEnv, @@ -3024,6 +3037,7 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => { "ai", "ai_search_namespace", "ai_search", + "agent_memory", "kv_namespace", "durable_object_namespace", "d1_database", @@ -4173,6 +4187,40 @@ const validateAISearchBinding: ValidatorFn = (diagnostics, field, value) => { return isValid; }; +const validateAgentMemoryBinding: ValidatorFn = (diagnostics, field, value) => { + if (typeof value !== "object" || value === null) { + diagnostics.errors.push( + `"agent_memory" bindings should be objects, but got ${JSON.stringify(value)}` + ); + return false; + } + let isValid = true; + if (!isRequiredProperty(value, "binding", "string")) { + diagnostics.errors.push( + `"${field}" bindings should have a string "binding" field but got ${JSON.stringify(value)}.` + ); + isValid = false; + } + if (!isRequiredProperty(value, "namespace", "string")) { + diagnostics.errors.push( + `"${field}" bindings must have a "namespace" field but got ${JSON.stringify(value)}.` + ); + isValid = false; + } + + if (!isRemoteValid(value, field, diagnostics)) { + isValid = false; + } + + validateAdditionalProperties(diagnostics, field, Object.keys(value), [ + "binding", + "namespace", + "remote", + ]); + + return isValid; +}; + const validateHyperdriveBinding: ValidatorFn = (diagnostics, field, value) => { if (typeof value !== "object" || value === null) { diagnostics.errors.push( diff --git a/packages/workers-utils/src/map-worker-metadata-bindings.ts b/packages/workers-utils/src/map-worker-metadata-bindings.ts index b8b2776122..344f174abb 100644 --- a/packages/workers-utils/src/map-worker-metadata-bindings.ts +++ b/packages/workers-utils/src/map-worker-metadata-bindings.ts @@ -299,6 +299,15 @@ export function mapWorkerMetadataBindings( }, ]; break; + case "agent_memory": + configObj.agent_memory = [ + ...(configObj.agent_memory ?? []), + { + binding: binding.name, + namespace: binding.namespace, + }, + ]; + break; case "hyperdrive": configObj.hyperdrive = [ ...(configObj.hyperdrive ?? []), diff --git a/packages/workers-utils/src/types.ts b/packages/workers-utils/src/types.ts index aa5882c379..99d37ddfa0 100644 --- a/packages/workers-utils/src/types.ts +++ b/packages/workers-utils/src/types.ts @@ -8,6 +8,7 @@ import type { } from "./config/environment"; import type { CfAIBinding, + CfAgentMemory, CfAISearch, CfAISearchNamespace, CfAnalyticsEngineDataset, @@ -73,6 +74,7 @@ export type WorkerMetadataBinding = | { type: "data_blob"; name: string; part: string } | { type: "ai_search_namespace"; name: string; namespace: string } | { type: "ai_search"; name: string; instance_name: string } + | { type: "agent_memory"; name: string; namespace: string } | { type: "kv_namespace"; name: string; namespace_id: string; raw?: boolean } | { type: "media"; name: string } | { @@ -334,6 +336,7 @@ export type Binding = | ({ type: "vectorize" } & BindingOmit) | ({ type: "ai_search_namespace" } & BindingOmit) | ({ type: "ai_search" } & BindingOmit) + | ({ type: "agent_memory" } & BindingOmit) | ({ type: "hyperdrive" } & BindingOmit) | ({ type: "service" } & BindingOmit) | { type: "fetcher"; fetcher: ServiceFetch } diff --git a/packages/workers-utils/src/worker.ts b/packages/workers-utils/src/worker.ts index 06925fe971..f8c91d9cff 100644 --- a/packages/workers-utils/src/worker.ts +++ b/packages/workers-utils/src/worker.ts @@ -242,6 +242,12 @@ export interface CfAISearch { remote?: boolean; } +export interface CfAgentMemory { + binding: string; + namespace: string | typeof INHERIT_SYMBOL; + remote?: boolean; +} + export interface CfSecretsStoreSecrets { binding: string; store_id: string; From 09134faa8065cfa99555f331e9c51b266d849e5d Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 1 Apr 2026 07:07:39 +0100 Subject: [PATCH 04/29] [wrangler] Wire remote-only agent_memory bindings through dev and deploy --- .changeset/agent-memory-binding-support.md | 6 + .../src/plugins/agent-memory/index.ts | 72 ++++++++++ packages/miniflare/src/plugins/index.ts | 4 + .../miniflare-remote-resources.test.ts | 28 ++++ .../remote-binding/workers/agent-memory.js | 7 + .../bindings.test.ts | 47 +++++++ .../src/__tests__/print-bindings.test.ts | 13 ++ .../src/__tests__/type-generation.test.ts | 6 + .../wrangler/src/agent-memory/provisioning.ts | 51 +++++++ .../wrangler/src/api/startDevWorker/utils.ts | 17 +++ .../deploy/check-remote-secrets-override.ts | 1 + packages/wrangler/src/deploy/config-diffs.ts | 1 + .../src/deployment-bundle/bindings.ts | 128 +++++++++++++++++- .../create-worker-upload-form.ts | 23 ++++ packages/wrangler/src/dev/miniflare/index.ts | 16 +++ packages/wrangler/src/utils/print-bindings.ts | 12 ++ 16 files changed, 427 insertions(+), 5 deletions(-) create mode 100644 .changeset/agent-memory-binding-support.md create mode 100644 packages/miniflare/src/plugins/agent-memory/index.ts create mode 100644 packages/wrangler/e2e/remote-binding/workers/agent-memory.js create mode 100644 packages/wrangler/src/agent-memory/provisioning.ts diff --git a/.changeset/agent-memory-binding-support.md b/.changeset/agent-memory-binding-support.md new file mode 100644 index 0000000000..05ba1a2ddf --- /dev/null +++ b/.changeset/agent-memory-binding-support.md @@ -0,0 +1,6 @@ +--- +"miniflare": minor +"wrangler": minor +--- + +Add remote-only `agent_memory` binding support in Wrangler and Miniflare, including config validation, deployment provisioning, `wrangler dev` wiring, and remote proxy support. diff --git a/packages/miniflare/src/plugins/agent-memory/index.ts b/packages/miniflare/src/plugins/agent-memory/index.ts new file mode 100644 index 0000000000..de1b34de59 --- /dev/null +++ b/packages/miniflare/src/plugins/agent-memory/index.ts @@ -0,0 +1,72 @@ +import { z } from "zod"; +import { + getUserBindingServiceName, + Plugin, + ProxyNodeBinding, + remoteProxyClientWorker, + RemoteProxyConnectionString, +} from "../shared"; + +const AgentMemoryEntrySchema = z.object({ + namespace: z.string(), + remoteProxyConnectionString: z + .custom() + .optional(), +}); + +export const AgentMemoryOptionsSchema = z.object({ + agentMemory: z.record(AgentMemoryEntrySchema).optional(), +}); + +export const AGENT_MEMORY_PLUGIN_NAME = "agent-memory"; + +const AGENT_MEMORY_SCOPE = "agent-memory"; + +export const AGENT_MEMORY_PLUGIN: Plugin = { + options: AgentMemoryOptionsSchema, + async getBindings(options) { + if (!options.agentMemory) { + return []; + } + + return Object.entries(options.agentMemory).map(([bindingName, entry]) => ({ + name: bindingName, + service: { + name: getUserBindingServiceName( + AGENT_MEMORY_SCOPE, + bindingName, + entry.remoteProxyConnectionString + ), + }, + })); + }, + getNodeBindings(options) { + if (!options.agentMemory) { + return {}; + } + + return Object.fromEntries( + Object.keys(options.agentMemory).map((bindingName) => [ + bindingName, + new ProxyNodeBinding(), + ]) + ); + }, + async getServices({ options }) { + if (!options.agentMemory) { + return []; + } + + return Object.entries(options.agentMemory).map(([bindingName, entry]) => ({ + name: getUserBindingServiceName( + AGENT_MEMORY_SCOPE, + bindingName, + entry.remoteProxyConnectionString + ), + worker: remoteProxyClientWorker( + entry.remoteProxyConnectionString, + bindingName + ), + })); + }, +}; diff --git a/packages/miniflare/src/plugins/index.ts b/packages/miniflare/src/plugins/index.ts index 7faa127c4f..aa3a024cce 100644 --- a/packages/miniflare/src/plugins/index.ts +++ b/packages/miniflare/src/plugins/index.ts @@ -1,3 +1,4 @@ +import { AGENT_MEMORY_PLUGIN, AGENT_MEMORY_PLUGIN_NAME } from "./agent-memory"; import { AI_PLUGIN, AI_PLUGIN_NAME } from "./ai"; import { AI_SEARCH_PLUGIN, AI_SEARCH_PLUGIN_NAME } from "./ai-search"; import { @@ -66,6 +67,7 @@ export const PLUGINS = { [EMAIL_PLUGIN_NAME]: EMAIL_PLUGIN, [ANALYTICS_ENGINE_PLUGIN_NAME]: ANALYTICS_ENGINE_PLUGIN, [AI_PLUGIN_NAME]: AI_PLUGIN, + [AGENT_MEMORY_PLUGIN_NAME]: AGENT_MEMORY_PLUGIN, [AI_SEARCH_PLUGIN_NAME]: AI_SEARCH_PLUGIN, [BROWSER_RENDERING_PLUGIN_NAME]: BROWSER_RENDERING_PLUGIN, [DISPATCH_NAMESPACE_PLUGIN_NAME]: DISPATCH_NAMESPACE_PLUGIN, @@ -135,6 +137,7 @@ export type WorkerOptions = z.input & z.input & z.input & z.input & + z.input & z.input & z.input & z.input & @@ -226,6 +229,7 @@ export * from "./secret-store"; export * from "./email"; export * from "./analytics-engine"; export * from "./ai"; +export * from "./agent-memory"; export * from "./ai-search"; export * from "./browser-rendering"; export * from "./dispatch-namespace"; diff --git a/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts b/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts index 47a26b02a6..15205f107e 100644 --- a/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts +++ b/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts @@ -482,6 +482,34 @@ const testCases: TestCase[] = [ }, getExpectFetchToMatch: (expect) => [expect.stringContaining(`"id"`)], }, + { + name: "Agent Memory", + scriptPath: "agent-memory.js", + setup: async (helper) => { + const namespace = generateResourceName("agent-memory"); + await helper.run(`wrangler agent-memory namespace create ${namespace}`); + + return { + remoteProxySessionConfig: { + bindings: { + MEMORY: { + type: "agent_memory", + namespace, + }, + }, + }, + miniflareConfig: (connection) => ({ + agentMemory: { + MEMORY: { + namespace, + remoteProxyConnectionString: connection, + }, + }, + }), + }; + }, + getExpectFetchToMatch: (expect) => [expect.stringContaining(`"context"`)], + }, { name: "Pipelines", scriptPath: "pipelines.js", diff --git a/packages/wrangler/e2e/remote-binding/workers/agent-memory.js b/packages/wrangler/e2e/remote-binding/workers/agent-memory.js new file mode 100644 index 0000000000..8f47bd1c80 --- /dev/null +++ b/packages/wrangler/e2e/remote-binding/workers/agent-memory.js @@ -0,0 +1,7 @@ +export default { + async fetch(_request, env) { + const memoryContext = env.MEMORY.getContext("wrangler-e2e"); + const summary = await memoryContext.getSummary(); + return new Response(JSON.stringify(summary)); + }, +}; diff --git a/packages/wrangler/src/__tests__/create-worker-upload-form/bindings.test.ts b/packages/wrangler/src/__tests__/create-worker-upload-form/bindings.test.ts index 31bc6eb965..412fcfd657 100644 --- a/packages/wrangler/src/__tests__/create-worker-upload-form/bindings.test.ts +++ b/packages/wrangler/src/__tests__/create-worker-upload-form/bindings.test.ts @@ -300,6 +300,10 @@ describe("createWorkerUploadForm — bindings", () => { type: "ai_search" as const, instance_name: "cloudflare-blog", }, + { + type: "agent_memory" as const, + namespace: "my-agent", + }, { type: "inherit" as const }, ])("should pass through $type binding unchanged", (input, { expect }) => { const bindings: StartDevWorkerInput["bindings"] = { @@ -373,6 +377,49 @@ describe("createWorkerUploadForm — bindings", () => { }); }); + describe("agent_memory bindings", () => { + it("should include agent_memory binding with namespace", ({ expect }) => { + const bindings: StartDevWorkerInput["bindings"] = { + MEMORY: { + type: "agent_memory", + namespace: "my-agent", + }, + }; + const form = createWorkerUploadForm(createEsmWorker(), bindings); + expect(getBindings(form)).toContainEqual({ + name: "MEMORY", + type: "agent_memory", + namespace: "my-agent", + }); + }); + + it("should throw when agent_memory has no namespace and not in dry run", ({ + expect, + }) => { + const bindings: StartDevWorkerInput["bindings"] = { + MEMORY: { type: "agent_memory" } as never, + }; + expect(() => + createWorkerUploadForm(createEsmWorker(), bindings) + ).toThrowError('MEMORY bindings must have a "namespace" field'); + }); + + it("should convert agent_memory to inherit binding during dry run when namespace is missing", ({ + expect, + }) => { + const bindings: StartDevWorkerInput["bindings"] = { + MEMORY: { type: "agent_memory" } as never, + }; + const form = createWorkerUploadForm(createEsmWorker(), bindings, { + dryRun: true, + }); + expect(getBindings(form)).toContainEqual({ + name: "MEMORY", + type: "inherit", + }); + }); + }); + describe("pipeline bindings", () => { it("should transform type from pipeline to pipelines", ({ expect }) => { const bindings: StartDevWorkerInput["bindings"] = { diff --git a/packages/wrangler/src/__tests__/print-bindings.test.ts b/packages/wrangler/src/__tests__/print-bindings.test.ts index e6e4f37ec4..c66afb6d81 100644 --- a/packages/wrangler/src/__tests__/print-bindings.test.ts +++ b/packages/wrangler/src/__tests__/print-bindings.test.ts @@ -114,6 +114,19 @@ describe("printBindings — AI Search bindings", () => { expect(output).toContain("(inherited)"); expect(output).not.toContain("Symbol(inherit_binding)"); }); + + it("shows Agent Memory bindings", ({ expect }) => { + const output = callPrintBindings({ + MEMORY: { + type: "agent_memory", + namespace: "my-agent", + }, + }); + + expect(output).toContain("MEMORY"); + expect(output).toContain("Agent Memory"); + expect(output).toContain("my-agent"); + }); }); describe("printBindings -- Artifacts bindings", () => { diff --git a/packages/wrangler/src/__tests__/type-generation.test.ts b/packages/wrangler/src/__tests__/type-generation.test.ts index 2a097d9aab..eb3e46dcec 100644 --- a/packages/wrangler/src/__tests__/type-generation.test.ts +++ b/packages/wrangler/src/__tests__/type-generation.test.ts @@ -462,6 +462,12 @@ const bindingsConfigMock: Omit< instance_name: "cloudflare-blog", }, ], + agent_memory: [ + { + binding: "AGENT_MEMORY_BINDING", + namespace: "my-agent", + }, + ], hyperdrive: [{ binding: "HYPERDRIVE_BINDING", id: "HYPERDRIVE_ID" }], mtls_certificates: [ { binding: "MTLS_BINDING", certificate_id: "MTLS_CERTIFICATE_ID" }, diff --git a/packages/wrangler/src/agent-memory/provisioning.ts b/packages/wrangler/src/agent-memory/provisioning.ts new file mode 100644 index 0000000000..8b3a48bb85 --- /dev/null +++ b/packages/wrangler/src/agent-memory/provisioning.ts @@ -0,0 +1,51 @@ +import { APIError, type ComplianceConfig } from "@cloudflare/workers-utils"; +import { fetchResult } from "../cfetch"; + +export interface AgentMemoryNamespace { + id: string; + name: string; +} + +/** + * Get an Agent Memory namespace for the given account. + * Throws an APIError (status 404) if the namespace does not exist. + */ +export async function getAgentMemoryNamespace( + complianceConfig: ComplianceConfig, + accountId: string, + namespaceName: string +): Promise { + try { + return await fetchResult( + complianceConfig, + `/accounts/${accountId}/agentmemory/namespaces/${namespaceName}`, + { method: "GET" } + ); + } catch (e) { + if (e instanceof APIError && e.status === 404) { + // Namespace does not exist - provision it + return null; + } + throw e; + } +} + +/** + * Create an Agent Memory namespace for the given account. + * Used by the provisioning system when a namespace doesn't exist at deploy time. + */ +export async function createAgentMemoryNamespace( + complianceConfig: ComplianceConfig, + accountId: string, + namespaceName: string +): Promise { + await fetchResult( + complianceConfig, + `/accounts/${accountId}/agentmemory/namespaces`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name: namespaceName }), + } + ); +} diff --git a/packages/wrangler/src/api/startDevWorker/utils.ts b/packages/wrangler/src/api/startDevWorker/utils.ts index 55c49b62d8..62fbdf92a6 100644 --- a/packages/wrangler/src/api/startDevWorker/utils.ts +++ b/packages/wrangler/src/api/startDevWorker/utils.ts @@ -320,6 +320,12 @@ export function convertConfigToBindings( } break; } + case "agent_memory": { + for (const { binding, ...x } of info) { + output[binding] = { type: "agent_memory", ...x }; + } + break; + } case "unsafe": { if (pages) { break; @@ -662,6 +668,17 @@ export function convertWorkerMetadataBindingsToFlatBindings( }; break; } + case "agent_memory": { + const b = binding as Extract< + WorkerMetadataBinding, + { type: "agent_memory" } + >; + output[name] = { + type: "agent_memory", + namespace: b.namespace, + }; + break; + } case "hyperdrive": { const b = binding as Extract< WorkerMetadataBinding, diff --git a/packages/wrangler/src/deploy/check-remote-secrets-override.ts b/packages/wrangler/src/deploy/check-remote-secrets-override.ts index a3763de30f..f94c3aceb6 100644 --- a/packages/wrangler/src/deploy/check-remote-secrets-override.ts +++ b/packages/wrangler/src/deploy/check-remote-secrets-override.ts @@ -114,6 +114,7 @@ function extractBindingNames(config: Config): string[] { case "vectorize": case "ai_search_namespaces": case "ai_search": + case "agent_memory": case "services": case "mtls_certificates": case "dispatch_namespaces": diff --git a/packages/wrangler/src/deploy/config-diffs.ts b/packages/wrangler/src/deploy/config-diffs.ts index e7e4f32149..1c502c3f05 100644 --- a/packages/wrangler/src/deploy/config-diffs.ts +++ b/packages/wrangler/src/deploy/config-diffs.ts @@ -24,6 +24,7 @@ const reorderableBindings = { vectorize: true, ai_search_namespaces: true, ai_search: true, + agent_memory: true, hyperdrive: true, workflows: true, dispatch_namespaces: true, diff --git a/packages/wrangler/src/deployment-bundle/bindings.ts b/packages/wrangler/src/deployment-bundle/bindings.ts index cf328b0a9d..cca5ec9935 100644 --- a/packages/wrangler/src/deployment-bundle/bindings.ts +++ b/packages/wrangler/src/deployment-bundle/bindings.ts @@ -7,6 +7,10 @@ import { PatchConfigError, UserError, } from "@cloudflare/workers-utils"; +import { + createAgentMemoryNamespace, + getAgentMemoryNamespace, +} from "../agent-memory/provisioning"; import { createAISearchNamespace, getAISearchNamespace } from "../ai-search"; import { convertConfigToBindings } from "../api/startDevWorker/utils"; import { fetchResult } from "../cfetch"; @@ -27,6 +31,7 @@ import { printBindings } from "../utils/print-bindings"; import { useServiceEnvironments } from "../utils/useServiceEnvironments"; import type { Binding, StartDevWorkerInput } from "../api/startDevWorker/types"; import type { + CfAgentMemory, CfAISearchNamespace, CfD1Database, CfKvNamespace, @@ -276,6 +281,67 @@ class AISearchNamespaceHandler extends ProvisionResourceHandler< } } +class AgentMemoryNamespaceHandler extends ProvisionResourceHandler< + "agent_memory", + Extract +> { + get name(): string | undefined { + return this.binding.namespace as string; + } + + async create(name: string) { + await createAgentMemoryNamespace( + this.complianceConfig, + this.accountId, + name + ); + return name; + } + + constructor( + bindingName: string, + binding: Extract, + complianceConfig: ComplianceConfig, + accountId: string + ) { + super( + "agent_memory", + bindingName, + binding, + "namespace", + complianceConfig, + accountId + ); + } + + canInherit(settings: Settings | undefined): boolean { + return !!settings?.bindings.find( + (existing) => + existing.type === this.type && + existing.name === this.bindingName && + (this.binding.namespace + ? this.binding.namespace === existing.namespace + : true) + ); + } + + async isConnectedToExistingResource(): Promise { + assert(typeof this.binding.namespace !== "symbol"); + + if (!this.binding.namespace) { + return false; + } + + const namespace = await getAgentMemoryNamespace( + this.complianceConfig, + this.accountId, + this.binding.namespace + ); + + return namespace !== null; + } +} + class KVHandler extends ProvisionResourceHandler< "kv_namespace", Extract @@ -402,7 +468,8 @@ type ProvisionableBinding = | Extract | Extract | Extract - | Extract; + | Extract + | Extract; const HANDLERS = { kv_namespace: { @@ -502,12 +569,44 @@ const HANDLERS = { }; }, }, + agent_memory: { + Handler: AgentMemoryNamespaceHandler, + sort: 4, + name: "Agent Memory", + keyDescription: "namespace name", + configField: "agent_memory" as const, + load: async (_complianceConfig: ComplianceConfig, _accountId: string) => { + // Agent Memory namespaces don't have a general list API in this context. + // The provisioning system will create them if they don't exist. + return []; + }, + toConfig: ( + bindingName: string, + binding: Extract + ): CfAgentMemory => { + const { type: _, ...rest } = binding; + return { + ...rest, + binding: bindingName, + }; + }, + }, }; type PendingResource = { binding: string; - resourceType: "kv_namespace" | "d1" | "r2_bucket" | "ai_search_namespace"; - handler: KVHandler | D1Handler | R2Handler | AISearchNamespaceHandler; + resourceType: + | "kv_namespace" + | "d1" + | "r2_bucket" + | "ai_search_namespace" + | "agent_memory"; + handler: + | KVHandler + | D1Handler + | R2Handler + | AISearchNamespaceHandler + | AgentMemoryNamespaceHandler; }; function isProvisionableBinding( @@ -521,7 +620,12 @@ function createHandler( binding: ProvisionableBinding, complianceConfig: ComplianceConfig, accountId: string -): KVHandler | D1Handler | R2Handler | AISearchNamespaceHandler { +): + | KVHandler + | D1Handler + | R2Handler + | AISearchNamespaceHandler + | AgentMemoryNamespaceHandler { switch (binding.type) { case "kv_namespace": return new KVHandler(bindingName, binding, complianceConfig, accountId); @@ -536,13 +640,25 @@ function createHandler( complianceConfig, accountId ); + case "agent_memory": + return new AgentMemoryNamespaceHandler( + bindingName, + binding, + complianceConfig, + accountId + ); } } function toConfigBinding( bindingName: string, binding: ProvisionableBinding -): CfKvNamespace | CfR2Bucket | CfD1Database | CfAISearchNamespace { +): + | CfKvNamespace + | CfR2Bucket + | CfD1Database + | CfAISearchNamespace + | CfAgentMemory { switch (binding.type) { case "kv_namespace": return HANDLERS.kv_namespace.toConfig(bindingName, binding); @@ -552,6 +668,8 @@ function toConfigBinding( return HANDLERS.r2_bucket.toConfig(bindingName, binding); case "ai_search_namespace": return HANDLERS.ai_search_namespace.toConfig(bindingName, binding); + case "agent_memory": + return HANDLERS.agent_memory.toConfig(bindingName, binding); } } diff --git a/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts b/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts index 2e7c6fae39..8da98dff70 100644 --- a/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts +++ b/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts @@ -131,6 +131,7 @@ export function createWorkerUploadForm( bindings ); const ai_search = extractBindingsOfType("ai_search", bindings); + const agent_memory = extractBindingsOfType("agent_memory", bindings); const hyperdrive = extractBindingsOfType("hyperdrive", bindings); const secrets_store_secrets = extractBindingsOfType( "secrets_store_secret", @@ -367,6 +368,28 @@ export function createWorkerUploadForm( }); }); + agent_memory.forEach(({ binding, namespace }) => { + if (options?.dryRun) { + namespace ??= INHERIT_SYMBOL; + } + if (namespace === undefined) { + throw new UserError(`${binding} bindings must have a "namespace" field`); + } + + if (namespace === INHERIT_SYMBOL) { + metadataBindings.push({ + name: binding, + type: "inherit", + }); + } else { + metadataBindings.push({ + name: binding, + type: "agent_memory", + namespace, + }); + } + }); + hyperdrive.forEach(({ binding, id }) => { metadataBindings.push({ name: binding, diff --git a/packages/wrangler/src/dev/miniflare/index.ts b/packages/wrangler/src/dev/miniflare/index.ts index c37dbee64a..fa4437eb63 100644 --- a/packages/wrangler/src/dev/miniflare/index.ts +++ b/packages/wrangler/src/dev/miniflare/index.ts @@ -403,6 +403,7 @@ type WorkerOptionsBindings = Pick< | "ai" | "aiSearchNamespaces" | "aiSearchInstances" + | "agentMemory" | "textBlobBindings" | "dataBlobBindings" | "wasmBindings" @@ -523,6 +524,7 @@ export function buildMiniflareBindingOptions( bindings ); const aiSearchInstanceBindings = extractBindingsOfType("ai_search", bindings); + const agentMemoryBindings = extractBindingsOfType("agent_memory", bindings); const imagesBindings = extractBindingsOfType("images", bindings); const mediaBindings = extractBindingsOfType("media", bindings); const browserBindings = extractBindingsOfType("browser", bindings); @@ -631,6 +633,10 @@ export function buildMiniflareBindingOptions( warnOrError("ai_search", inst.remote, "always-remote"); } + for (const memory of agentMemoryBindings) { + warnOrError("agent_memory", memory.remote, "always-remote"); + } + for (const media of mediaBindings) { warnOrError("media", media.remote, "always-remote"); } @@ -753,6 +759,16 @@ export function buildMiniflareBindingOptions( ]) ), + agentMemory: Object.fromEntries( + agentMemoryBindings.map((memory) => [ + memory.binding, + { + namespace: memory.namespace as string, + remoteProxyConnectionString, + }, + ]) + ), + kvNamespaces: Object.fromEntries( kvNamespaces.map((kv) => kvNamespaceEntry(kv, remoteProxyConnectionString) diff --git a/packages/wrangler/src/utils/print-bindings.ts b/packages/wrangler/src/utils/print-bindings.ts index baa18c001b..62d0f72ae2 100644 --- a/packages/wrangler/src/utils/print-bindings.ts +++ b/packages/wrangler/src/utils/print-bindings.ts @@ -88,6 +88,7 @@ export function printBindings( bindings ); const ai_search = extractBindingsOfType("ai_search", bindings); + const agent_memory = extractBindingsOfType("agent_memory", bindings); const hyperdrive = extractBindingsOfType("hyperdrive", bindings); const r2_buckets = extractBindingsOfType("r2_bucket", bindings); const logfwdr = extractBindingsOfType("logfwdr", bindings); @@ -353,6 +354,17 @@ export function printBindings( ); } + if (agent_memory.length > 0) { + output.push( + ...agent_memory.map(({ binding, namespace }) => ({ + name: binding, + type: getBindingTypeFriendlyName("agent_memory"), + value: namespace ? String(namespace) : undefined, + mode: getMode({ isSimulatedLocally: false }), + })) + ); + } + if (hyperdrive.length > 0) { output.push( ...hyperdrive.map(({ binding, id }) => { From 6ba6871b020a7cbb01edfc811da6892c87cdeb4c Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 1 Apr 2026 18:23:37 +0100 Subject: [PATCH 05/29] [wrangler] Add type generation, tests, and config-diffs fix for agent_memory binding Add agent_memory to wrangler types generation so running `wrangler types` produces typed AgentMemory bindings. Fix config-diffs to strip the remote field when comparing local vs remote configs. Fix print-bindings to correctly display inherited agent_memory bindings. Add comprehensive tests: config validation (7 tests), provisioning (3 tests), deploy binding payload (1 test), and update type generation snapshots. --- .../normalize-and-validate-config.test.ts | 132 +++++++++++++++++ .../src/__tests__/deploy/bindings.test.ts | 49 +++++++ .../wrangler/src/__tests__/provision.test.ts | 134 ++++++++++++++++++ .../src/__tests__/type-generation.test.ts | 3 + packages/wrangler/src/deploy/config-diffs.ts | 6 + .../wrangler/src/type-generation/index.ts | 43 ++++++ packages/wrangler/src/utils/print-bindings.ts | 2 +- 7 files changed, 368 insertions(+), 1 deletion(-) diff --git a/packages/workers-utils/tests/config/validation/normalize-and-validate-config.test.ts b/packages/workers-utils/tests/config/validation/normalize-and-validate-config.test.ts index 578ddb15b6..ed47732049 100644 --- a/packages/workers-utils/tests/config/validation/normalize-and-validate-config.test.ts +++ b/packages/workers-utils/tests/config/validation/normalize-and-validate-config.test.ts @@ -37,6 +37,7 @@ describe("normalizeAndValidateConfig()", () => { vectorize: [], ai_search_namespaces: [], ai_search: [], + agent_memory: [], hyperdrive: [], dev: { ip: process.platform === "win32" ? "127.0.0.1" : "localhost", @@ -2278,6 +2279,137 @@ describe("normalizeAndValidateConfig()", () => { { env: undefined } ); + expect(diagnostics.renderWarnings()).toContain("extra_field"); + }); + }); + + describe("[agent_memory]", () => { + it("should error if agent_memory is an object", ({ expect }) => { + const { diagnostics } = normalizeAndValidateConfig( + { agent_memory: {} } as unknown as RawConfig, + undefined, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field "agent_memory" should be an array but got {}." + `); + }); + + it("should error if agent_memory is a string", ({ expect }) => { + const { diagnostics } = normalizeAndValidateConfig( + { agent_memory: "BAD" } as unknown as RawConfig, + undefined, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field "agent_memory" should be an array but got "BAD"." + `); + }); + + it("should error if agent_memory is a number", ({ expect }) => { + const { diagnostics } = normalizeAndValidateConfig( + { agent_memory: 999 } as unknown as RawConfig, + undefined, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field "agent_memory" should be an array but got 999." + `); + }); + + it("should error if agent_memory is null", ({ expect }) => { + const { diagnostics } = normalizeAndValidateConfig( + { agent_memory: null } as unknown as RawConfig, + undefined, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field "agent_memory" should be an array but got null." + `); + }); + + it("should error if agent_memory bindings are not valid", ({ + expect, + }) => { + const { diagnostics } = normalizeAndValidateConfig( + { + agent_memory: [ + {}, + { binding: "VALID" }, + { binding: 2000, namespace: 2111 }, + { + binding: "BINDING_2", + namespace: "my-namespace", + }, + ], + } as unknown as RawConfig, + undefined, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - "agent_memory[0]" bindings should have a string "binding" field but got {}. + - "agent_memory[0]" bindings must have a "namespace" field but got {}. + - "agent_memory[1]" bindings must have a "namespace" field but got {"binding":"VALID"}. + - "agent_memory[2]" bindings should have a string "binding" field but got {"binding":2000,"namespace":2111}. + - "agent_memory[2]" bindings must have a "namespace" field but got {"binding":2000,"namespace":2111}." + `); + }); + + it("should accept valid agent_memory bindings", ({ expect }) => { + const { diagnostics } = normalizeAndValidateConfig( + { + agent_memory: [ + { + binding: "MEMORY", + namespace: "my-namespace", + }, + ], + } as unknown as RawConfig, + undefined, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.hasErrors()).toBe(false); + }); + + it("should error on additional properties", ({ expect }) => { + const { diagnostics } = normalizeAndValidateConfig( + { + agent_memory: [ + { + binding: "MEMORY", + namespace: "my-namespace", + extra_field: "unexpected", + }, + ], + } as unknown as RawConfig, + undefined, + undefined, + { env: undefined } + ); + expect(diagnostics.renderWarnings()).toContain("extra_field"); }); }); diff --git a/packages/wrangler/src/__tests__/deploy/bindings.test.ts b/packages/wrangler/src/__tests__/deploy/bindings.test.ts index 41493edfab..f9677a51fc 100644 --- a/packages/wrangler/src/__tests__/deploy/bindings.test.ts +++ b/packages/wrangler/src/__tests__/deploy/bindings.test.ts @@ -83,6 +83,15 @@ describe("deploy", () => { return HttpResponse.json(createFetchResult({})); }) ); + // Pretend all Agent Memory namespaces exist for the same reason. + msw.use( + http.get( + "*/accounts/:accountId/agentmemory/namespaces/:namespaceName", + async () => { + return HttpResponse.json(createFetchResult({})); + } + ) + ); vi.mocked(fetchSecrets).mockResolvedValue([]); vi.mocked(getInstalledPackageVersion).mockReturnValue(undefined); }); @@ -2149,6 +2158,46 @@ describe("deploy", () => { }); }); + describe("[agent_memory]", () => { + it("should support agent_memory bindings", async ({ expect }) => { + writeWranglerConfig({ + agent_memory: [ + { binding: "MEMORY", namespace: "my-agent-namespace" }, + ], + }); + writeWorkerSource(); + mockSubDomainRequest(); + mockUploadWorkerRequest({ + expectedBindings: [ + { + name: "MEMORY", + type: "agent_memory", + namespace: "my-agent-namespace", + }, + ], + }); + + await runWrangler("deploy index.js"); + expect(std.out).toMatchInlineSnapshot(` + " + ⛅️ wrangler x.x.x + ────────────────── + Total Upload: xx KiB / gzip: xx KiB + Worker Startup Time: 100 ms + Your Worker has access to the following bindings: + Binding Resource + env.MEMORY (my-agent-namespace) Agent Memory + + Uploaded test-name (TIMINGS) + Deployed test-name triggers (TIMINGS) + https://test-name.test-sub-domain.workers.dev + Current Version ID: Galaxy-Class" + `); + expect(std.err).toMatchInlineSnapshot(`""`); + expect(std.warn).toMatchInlineSnapshot(`""`); + }); + }); + describe("[unsafe]", () => { describe("[unsafe.bindings]", () => { it("should stringify object in unsafe metadata", async ({ expect }) => { diff --git a/packages/wrangler/src/__tests__/provision.test.ts b/packages/wrangler/src/__tests__/provision.test.ts index db51b43569..5edfa4ad14 100644 --- a/packages/wrangler/src/__tests__/provision.test.ts +++ b/packages/wrangler/src/__tests__/provision.test.ts @@ -1319,6 +1319,86 @@ describe("resource provisioning", () => { }); }); + describe("provisions agent_memory bindings", () => { + beforeEach(() => { + writeWranglerConfig({ + main: "index.js", + agent_memory: [{ binding: "MEMORY", namespace: "my-agent-namespace" }], + }); + }); + + it("should inherit agent_memory binding if found in the deployed settings", async ({ + expect, + }) => { + mockGetSettings({ + result: { + bindings: [ + { + type: "agent_memory", + name: "MEMORY", + namespace: "my-agent-namespace", + }, + ], + }, + }); + mockUploadWorkerRequest({ + expectedBindings: [ + { + name: "MEMORY", + type: "inherit", + }, + ], + }); + + await runWrangler("deploy"); + expect(std.out).toContain("env.MEMORY (inherited)"); + expect(std.err).toMatchInlineSnapshot(`""`); + }); + + it("should connect to existing agent_memory namespace if it already exists", async ({ + expect, + }) => { + mockGetSettings(); + mockGetAgentMemoryNamespace(expect, "my-agent-namespace", false); + mockUploadWorkerRequest({ + expectedBindings: [ + { + name: "MEMORY", + type: "agent_memory", + namespace: "my-agent-namespace", + }, + ], + }); + + await runWrangler("deploy"); + expect(std.out).toContain("env.MEMORY"); + expect(std.err).toMatchInlineSnapshot(`""`); + }); + + it("should create agent_memory namespace if it does not exist", async ({ + expect, + }) => { + mockGetSettings(); + mockGetAgentMemoryNamespace(expect, "my-agent-namespace", true); + mockCreateAgentMemoryNamespace(expect, { + assertName: "my-agent-namespace", + }); + mockUploadWorkerRequest({ + expectedBindings: [ + { + name: "MEMORY", + type: "agent_memory", + namespace: "my-agent-namespace", + }, + ], + }); + + await runWrangler("deploy"); + expect(std.out).toContain("env.MEMORY"); + expect(std.err).toMatchInlineSnapshot(`""`); + }); + }); + it("should error if used with a service environment", async ({ expect }) => { writeWorkerSource(); writeWranglerConfig({ @@ -1433,3 +1513,57 @@ function mockGetD1Database( ) ); } + +function mockGetAgentMemoryNamespace( + expect: ExpectStatic, + namespaceName: string, + missing: boolean = false +) { + msw.use( + http.get( + "*/accounts/:accountId/agentmemory/namespaces/:namespaceName", + async ({ params }) => { + expect(params.namespaceName).toEqual(namespaceName); + if (missing) { + return HttpResponse.json( + createFetchResult(null, false, [ + { code: 10006, message: "namespace not found" }, + ]), + { status: 404 } + ); + } + return HttpResponse.json( + createFetchResult({ + id: "agent-memory-namespace-id", + name: namespaceName, + }) + ); + }, + { once: true } + ) + ); +} + +function mockCreateAgentMemoryNamespace( + expect: ExpectStatic, + options: { assertName?: string } = {} +) { + msw.use( + http.post( + "*/accounts/:accountId/agentmemory/namespaces", + async ({ request }) => { + if (options.assertName) { + const requestBody = await request.json(); + expect(requestBody).toEqual({ name: options.assertName }); + } + return HttpResponse.json( + createFetchResult({ + id: "new-agent-memory-namespace-id", + name: options.assertName ?? "test-namespace", + }) + ); + }, + { once: true } + ) + ); +} diff --git a/packages/wrangler/src/__tests__/type-generation.test.ts b/packages/wrangler/src/__tests__/type-generation.test.ts index eb3e46dcec..263f792b11 100644 --- a/packages/wrangler/src/__tests__/type-generation.test.ts +++ b/packages/wrangler/src/__tests__/type-generation.test.ts @@ -794,6 +794,7 @@ describe("generate types - CLI", () => { VPC_SERVICE_BINDING: Fetcher; AI_SEARCH_NS_BINDING: AiSearchNamespace; AI_SEARCH_BINDING: AiSearchInstance; + AGENT_MEMORY_BINDING: AgentMemory; LOGFWDR_SCHEMA: any; BROWSER_BINDING: Fetcher; AI_BINDING: Ai; @@ -913,6 +914,7 @@ describe("generate types - CLI", () => { VPC_SERVICE_BINDING: Fetcher; AI_SEARCH_NS_BINDING: AiSearchNamespace; AI_SEARCH_BINDING: AiSearchInstance; + AGENT_MEMORY_BINDING: AgentMemory; LOGFWDR_SCHEMA: any; BROWSER_BINDING: Fetcher; AI_BINDING: Ai; @@ -1095,6 +1097,7 @@ describe("generate types - CLI", () => { VPC_SERVICE_BINDING: Fetcher; AI_SEARCH_NS_BINDING: AiSearchNamespace; AI_SEARCH_BINDING: AiSearchInstance; + AGENT_MEMORY_BINDING: AgentMemory; LOGFWDR_SCHEMA: any; BROWSER_BINDING: Fetcher; AI_BINDING: Ai; diff --git a/packages/wrangler/src/deploy/config-diffs.ts b/packages/wrangler/src/deploy/config-diffs.ts index 1c502c3f05..0301aa8936 100644 --- a/packages/wrangler/src/deploy/config-diffs.ts +++ b/packages/wrangler/src/deploy/config-diffs.ts @@ -213,6 +213,12 @@ function removeRemoteConfigFieldFromBindings(normalizedConfig: Config): void { ); } + if (normalizedConfig.agent_memory?.length) { + normalizedConfig.agent_memory = normalizedConfig.agent_memory.map( + ({ remote: _, ...binding }) => binding + ); + } + if (normalizedConfig.queues?.producers?.length) { normalizedConfig.queues.producers = normalizedConfig.queues.producers.map( ({ remote: _, ...binding }) => binding diff --git a/packages/wrangler/src/type-generation/index.ts b/packages/wrangler/src/type-generation/index.ts index 0eb549b291..11b27c6e99 100644 --- a/packages/wrangler/src/type-generation/index.ts +++ b/packages/wrangler/src/type-generation/index.ts @@ -2338,6 +2338,28 @@ function collectCoreBindings( addBinding(aiSearch.binding, "AiSearchInstance", "ai_search", envName); } + for (const [index, agentMemory] of ( + env.agent_memory ?? [] + ).entries()) { + if (!agentMemory.binding) { + throwMissingBindingError({ + binding: agentMemory, + bindingType: "agent_memory", + configPath: args.config, + envName, + fieldName: "binding", + index, + }); + } + + addBinding( + agentMemory.binding, + "AgentMemory", + "agent_memory", + envName + ); + } + // Pipelines handled separately for async schema fetching if (env.logfwdr?.bindings?.length) { @@ -3479,6 +3501,27 @@ function collectCoreBindingsPerEnvironment( }); } + for (const [index, agentMemory] of ( + env.agent_memory ?? [] + ).entries()) { + if (!agentMemory.binding) { + throwMissingBindingError({ + binding: agentMemory, + bindingType: "agent_memory", + configPath: args.config, + envName, + fieldName: "binding", + index, + }); + } + + bindings.push({ + bindingCategory: "agent_memory", + name: agentMemory.binding, + type: "AgentMemory", + }); + } + return bindings; } diff --git a/packages/wrangler/src/utils/print-bindings.ts b/packages/wrangler/src/utils/print-bindings.ts index 62d0f72ae2..569cb76c5d 100644 --- a/packages/wrangler/src/utils/print-bindings.ts +++ b/packages/wrangler/src/utils/print-bindings.ts @@ -359,7 +359,7 @@ export function printBindings( ...agent_memory.map(({ binding, namespace }) => ({ name: binding, type: getBindingTypeFriendlyName("agent_memory"), - value: namespace ? String(namespace) : undefined, + value: namespace ?? undefined, mode: getMode({ isSimulatedLocally: false }), })) ); From 6fa92512aca0b13f622187271c0b3a0442c0ef7d Mon Sep 17 00:00:00 2001 From: Oli Yu Date: Tue, 7 Apr 2026 12:36:15 -0500 Subject: [PATCH 06/29] fixes to use agent_memory_namespace type --- .../workers-utils/src/config/validation.ts | 4 ++-- .../src/map-worker-metadata-bindings.ts | 3 ++- packages/workers-utils/src/types.ts | 4 ++-- .../normalize-and-validate-config.test.ts | 4 ++-- .../miniflare-remote-resources.test.ts | 2 +- .../bindings.test.ts | 20 +++++++++-------- .../src/__tests__/print-bindings.test.ts | 2 +- .../wrangler/src/api/startDevWorker/utils.ts | 8 +++---- .../src/deployment-bundle/bindings.ts | 22 +++++++++---------- .../create-worker-upload-form.ts | 7 ++++-- packages/wrangler/src/dev/miniflare/index.ts | 7 ++++-- .../wrangler/src/type-generation/index.ts | 15 +++---------- packages/wrangler/src/utils/print-bindings.ts | 7 ++++-- 13 files changed, 54 insertions(+), 51 deletions(-) diff --git a/packages/workers-utils/src/config/validation.ts b/packages/workers-utils/src/config/validation.ts index 71246c2623..02147f8d22 100644 --- a/packages/workers-utils/src/config/validation.ts +++ b/packages/workers-utils/src/config/validation.ts @@ -183,7 +183,7 @@ const bindingTypeFriendlyNames: Record = { vectorize: "Vectorize Index", ai_search_namespace: "AI Search Namespace", ai_search: "AI Search Instance", - agent_memory: "Agent Memory", + agent_memory_namespace: "Agent Memory", hyperdrive: "Hyperdrive Config", service: "Worker", fetcher: "Service Binding", @@ -3037,7 +3037,7 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => { "ai", "ai_search_namespace", "ai_search", - "agent_memory", + "agent_memory_namespace", "kv_namespace", "durable_object_namespace", "d1_database", diff --git a/packages/workers-utils/src/map-worker-metadata-bindings.ts b/packages/workers-utils/src/map-worker-metadata-bindings.ts index 344f174abb..a830e08198 100644 --- a/packages/workers-utils/src/map-worker-metadata-bindings.ts +++ b/packages/workers-utils/src/map-worker-metadata-bindings.ts @@ -299,7 +299,7 @@ export function mapWorkerMetadataBindings( }, ]; break; - case "agent_memory": + case "agent_memory_namespace": { configObj.agent_memory = [ ...(configObj.agent_memory ?? []), { @@ -308,6 +308,7 @@ export function mapWorkerMetadataBindings( }, ]; break; + } case "hyperdrive": configObj.hyperdrive = [ ...(configObj.hyperdrive ?? []), diff --git a/packages/workers-utils/src/types.ts b/packages/workers-utils/src/types.ts index 99d37ddfa0..877219c614 100644 --- a/packages/workers-utils/src/types.ts +++ b/packages/workers-utils/src/types.ts @@ -74,7 +74,7 @@ export type WorkerMetadataBinding = | { type: "data_blob"; name: string; part: string } | { type: "ai_search_namespace"; name: string; namespace: string } | { type: "ai_search"; name: string; instance_name: string } - | { type: "agent_memory"; name: string; namespace: string } + | { type: "agent_memory_namespace"; name: string; namespace: string } | { type: "kv_namespace"; name: string; namespace_id: string; raw?: boolean } | { type: "media"; name: string } | { @@ -336,7 +336,7 @@ export type Binding = | ({ type: "vectorize" } & BindingOmit) | ({ type: "ai_search_namespace" } & BindingOmit) | ({ type: "ai_search" } & BindingOmit) - | ({ type: "agent_memory" } & BindingOmit) + | ({ type: "agent_memory_namespace" } & BindingOmit) | ({ type: "hyperdrive" } & BindingOmit) | ({ type: "service" } & BindingOmit) | { type: "fetcher"; fetcher: ServiceFetch } diff --git a/packages/workers-utils/tests/config/validation/normalize-and-validate-config.test.ts b/packages/workers-utils/tests/config/validation/normalize-and-validate-config.test.ts index ed47732049..6f7db447b5 100644 --- a/packages/workers-utils/tests/config/validation/normalize-and-validate-config.test.ts +++ b/packages/workers-utils/tests/config/validation/normalize-and-validate-config.test.ts @@ -2279,9 +2279,9 @@ describe("normalizeAndValidateConfig()", () => { { env: undefined } ); - expect(diagnostics.renderWarnings()).toContain("extra_field"); + expect(diagnostics.renderWarnings()).toContain("extra_field"); + }); }); - }); describe("[agent_memory]", () => { it("should error if agent_memory is an object", ({ expect }) => { diff --git a/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts b/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts index 15205f107e..c04e030d22 100644 --- a/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts +++ b/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts @@ -493,7 +493,7 @@ const testCases: TestCase[] = [ remoteProxySessionConfig: { bindings: { MEMORY: { - type: "agent_memory", + type: "agent_memory_namespace", namespace, }, }, diff --git a/packages/wrangler/src/__tests__/create-worker-upload-form/bindings.test.ts b/packages/wrangler/src/__tests__/create-worker-upload-form/bindings.test.ts index 412fcfd657..2387272f0c 100644 --- a/packages/wrangler/src/__tests__/create-worker-upload-form/bindings.test.ts +++ b/packages/wrangler/src/__tests__/create-worker-upload-form/bindings.test.ts @@ -301,7 +301,7 @@ describe("createWorkerUploadForm — bindings", () => { instance_name: "cloudflare-blog", }, { - type: "agent_memory" as const, + type: "agent_memory_namespace" as const, namespace: "my-agent", }, { type: "inherit" as const }, @@ -377,38 +377,40 @@ describe("createWorkerUploadForm — bindings", () => { }); }); - describe("agent_memory bindings", () => { - it("should include agent_memory binding with namespace", ({ expect }) => { + describe("agent_memory_namespace bindings", () => { + it("should include agent_memory_namespace binding with namespace", ({ + expect, + }) => { const bindings: StartDevWorkerInput["bindings"] = { MEMORY: { - type: "agent_memory", + type: "agent_memory_namespace", namespace: "my-agent", }, }; const form = createWorkerUploadForm(createEsmWorker(), bindings); expect(getBindings(form)).toContainEqual({ name: "MEMORY", - type: "agent_memory", + type: "agent_memory_namespace", namespace: "my-agent", }); }); - it("should throw when agent_memory has no namespace and not in dry run", ({ + it("should throw when agent_memory_namespace has no namespace and not in dry run", ({ expect, }) => { const bindings: StartDevWorkerInput["bindings"] = { - MEMORY: { type: "agent_memory" } as never, + MEMORY: { type: "agent_memory_namespace" } as never, }; expect(() => createWorkerUploadForm(createEsmWorker(), bindings) ).toThrowError('MEMORY bindings must have a "namespace" field'); }); - it("should convert agent_memory to inherit binding during dry run when namespace is missing", ({ + it("should convert agent_memory_namespace to inherit binding during dry run when namespace is missing", ({ expect, }) => { const bindings: StartDevWorkerInput["bindings"] = { - MEMORY: { type: "agent_memory" } as never, + MEMORY: { type: "agent_memory_namespace" } as never, }; const form = createWorkerUploadForm(createEsmWorker(), bindings, { dryRun: true, diff --git a/packages/wrangler/src/__tests__/print-bindings.test.ts b/packages/wrangler/src/__tests__/print-bindings.test.ts index c66afb6d81..075ff8cdf4 100644 --- a/packages/wrangler/src/__tests__/print-bindings.test.ts +++ b/packages/wrangler/src/__tests__/print-bindings.test.ts @@ -118,7 +118,7 @@ describe("printBindings — AI Search bindings", () => { it("shows Agent Memory bindings", ({ expect }) => { const output = callPrintBindings({ MEMORY: { - type: "agent_memory", + type: "agent_memory_namespace", namespace: "my-agent", }, }); diff --git a/packages/wrangler/src/api/startDevWorker/utils.ts b/packages/wrangler/src/api/startDevWorker/utils.ts index 62fbdf92a6..55b92ecb02 100644 --- a/packages/wrangler/src/api/startDevWorker/utils.ts +++ b/packages/wrangler/src/api/startDevWorker/utils.ts @@ -322,7 +322,7 @@ export function convertConfigToBindings( } case "agent_memory": { for (const { binding, ...x } of info) { - output[binding] = { type: "agent_memory", ...x }; + output[binding] = { type: "agent_memory_namespace", ...x }; } break; } @@ -668,13 +668,13 @@ export function convertWorkerMetadataBindingsToFlatBindings( }; break; } - case "agent_memory": { + case "agent_memory_namespace": { const b = binding as Extract< WorkerMetadataBinding, - { type: "agent_memory" } + { type: "agent_memory_namespace" } >; output[name] = { - type: "agent_memory", + type: "agent_memory_namespace", namespace: b.namespace, }; break; diff --git a/packages/wrangler/src/deployment-bundle/bindings.ts b/packages/wrangler/src/deployment-bundle/bindings.ts index cca5ec9935..62a7913721 100644 --- a/packages/wrangler/src/deployment-bundle/bindings.ts +++ b/packages/wrangler/src/deployment-bundle/bindings.ts @@ -282,8 +282,8 @@ class AISearchNamespaceHandler extends ProvisionResourceHandler< } class AgentMemoryNamespaceHandler extends ProvisionResourceHandler< - "agent_memory", - Extract + "agent_memory_namespace", + Extract > { get name(): string | undefined { return this.binding.namespace as string; @@ -300,12 +300,12 @@ class AgentMemoryNamespaceHandler extends ProvisionResourceHandler< constructor( bindingName: string, - binding: Extract, + binding: Extract, complianceConfig: ComplianceConfig, accountId: string ) { super( - "agent_memory", + "agent_memory_namespace", bindingName, binding, "namespace", @@ -469,7 +469,7 @@ type ProvisionableBinding = | Extract | Extract | Extract - | Extract; + | Extract; const HANDLERS = { kv_namespace: { @@ -569,7 +569,7 @@ const HANDLERS = { }; }, }, - agent_memory: { + agent_memory_namespace: { Handler: AgentMemoryNamespaceHandler, sort: 4, name: "Agent Memory", @@ -582,7 +582,7 @@ const HANDLERS = { }, toConfig: ( bindingName: string, - binding: Extract + binding: Extract ): CfAgentMemory => { const { type: _, ...rest } = binding; return { @@ -600,7 +600,7 @@ type PendingResource = { | "d1" | "r2_bucket" | "ai_search_namespace" - | "agent_memory"; + | "agent_memory_namespace"; handler: | KVHandler | D1Handler @@ -640,7 +640,7 @@ function createHandler( complianceConfig, accountId ); - case "agent_memory": + case "agent_memory_namespace": return new AgentMemoryNamespaceHandler( bindingName, binding, @@ -668,8 +668,8 @@ function toConfigBinding( return HANDLERS.r2_bucket.toConfig(bindingName, binding); case "ai_search_namespace": return HANDLERS.ai_search_namespace.toConfig(bindingName, binding); - case "agent_memory": - return HANDLERS.agent_memory.toConfig(bindingName, binding); + case "agent_memory_namespace": + return HANDLERS.agent_memory_namespace.toConfig(bindingName, binding); } } diff --git a/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts b/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts index 8da98dff70..86f15e0c69 100644 --- a/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts +++ b/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts @@ -131,7 +131,10 @@ export function createWorkerUploadForm( bindings ); const ai_search = extractBindingsOfType("ai_search", bindings); - const agent_memory = extractBindingsOfType("agent_memory", bindings); + const agent_memory = extractBindingsOfType( + "agent_memory_namespace", + bindings + ); const hyperdrive = extractBindingsOfType("hyperdrive", bindings); const secrets_store_secrets = extractBindingsOfType( "secrets_store_secret", @@ -384,7 +387,7 @@ export function createWorkerUploadForm( } else { metadataBindings.push({ name: binding, - type: "agent_memory", + type: "agent_memory_namespace", namespace, }); } diff --git a/packages/wrangler/src/dev/miniflare/index.ts b/packages/wrangler/src/dev/miniflare/index.ts index fa4437eb63..c26cc7c940 100644 --- a/packages/wrangler/src/dev/miniflare/index.ts +++ b/packages/wrangler/src/dev/miniflare/index.ts @@ -524,7 +524,10 @@ export function buildMiniflareBindingOptions( bindings ); const aiSearchInstanceBindings = extractBindingsOfType("ai_search", bindings); - const agentMemoryBindings = extractBindingsOfType("agent_memory", bindings); + const agentMemoryBindings = extractBindingsOfType( + "agent_memory_namespace", + bindings + ); const imagesBindings = extractBindingsOfType("images", bindings); const mediaBindings = extractBindingsOfType("media", bindings); const browserBindings = extractBindingsOfType("browser", bindings); @@ -634,7 +637,7 @@ export function buildMiniflareBindingOptions( } for (const memory of agentMemoryBindings) { - warnOrError("agent_memory", memory.remote, "always-remote"); + warnOrError("agent_memory_namespace", memory.remote, "always-remote"); } for (const media of mediaBindings) { diff --git a/packages/wrangler/src/type-generation/index.ts b/packages/wrangler/src/type-generation/index.ts index 11b27c6e99..882e0744fe 100644 --- a/packages/wrangler/src/type-generation/index.ts +++ b/packages/wrangler/src/type-generation/index.ts @@ -2338,9 +2338,7 @@ function collectCoreBindings( addBinding(aiSearch.binding, "AiSearchInstance", "ai_search", envName); } - for (const [index, agentMemory] of ( - env.agent_memory ?? [] - ).entries()) { + for (const [index, agentMemory] of (env.agent_memory ?? []).entries()) { if (!agentMemory.binding) { throwMissingBindingError({ binding: agentMemory, @@ -2352,12 +2350,7 @@ function collectCoreBindings( }); } - addBinding( - agentMemory.binding, - "AgentMemory", - "agent_memory", - envName - ); + addBinding(agentMemory.binding, "AgentMemory", "agent_memory", envName); } // Pipelines handled separately for async schema fetching @@ -3501,9 +3494,7 @@ function collectCoreBindingsPerEnvironment( }); } - for (const [index, agentMemory] of ( - env.agent_memory ?? [] - ).entries()) { + for (const [index, agentMemory] of (env.agent_memory ?? []).entries()) { if (!agentMemory.binding) { throwMissingBindingError({ binding: agentMemory, diff --git a/packages/wrangler/src/utils/print-bindings.ts b/packages/wrangler/src/utils/print-bindings.ts index 569cb76c5d..f97e76f6b0 100644 --- a/packages/wrangler/src/utils/print-bindings.ts +++ b/packages/wrangler/src/utils/print-bindings.ts @@ -88,7 +88,10 @@ export function printBindings( bindings ); const ai_search = extractBindingsOfType("ai_search", bindings); - const agent_memory = extractBindingsOfType("agent_memory", bindings); + const agent_memory = extractBindingsOfType( + "agent_memory_namespace", + bindings + ); const hyperdrive = extractBindingsOfType("hyperdrive", bindings); const r2_buckets = extractBindingsOfType("r2_bucket", bindings); const logfwdr = extractBindingsOfType("logfwdr", bindings); @@ -358,7 +361,7 @@ export function printBindings( output.push( ...agent_memory.map(({ binding, namespace }) => ({ name: binding, - type: getBindingTypeFriendlyName("agent_memory"), + type: getBindingTypeFriendlyName("agent_memory_namespace"), value: namespace ?? undefined, mode: getMode({ isSimulatedLocally: false }), })) From 1b3b6e96a74f4bac0dd0daff211ce360e5a1a6e6 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 10 Apr 2026 11:05:09 +0100 Subject: [PATCH 07/29] Improve changeset description to follow official guidelines --- .changeset/agent-memory-binding-support.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.changeset/agent-memory-binding-support.md b/.changeset/agent-memory-binding-support.md index 05ba1a2ddf..514cd39e4c 100644 --- a/.changeset/agent-memory-binding-support.md +++ b/.changeset/agent-memory-binding-support.md @@ -3,4 +3,21 @@ "wrangler": minor --- -Add remote-only `agent_memory` binding support in Wrangler and Miniflare, including config validation, deployment provisioning, `wrangler dev` wiring, and remote proxy support. +Add support for `agent_memory` bindings + +Agent Memory bindings allow Workers to connect to Cloudflare's Agent Memory service for storing and retrieving agent conversation state. This binding is remote-only, meaning it always connects to the Cloudflare API during `wrangler dev` rather than using a local simulation. + +To configure an `agent_memory` binding, add the following to your `wrangler.json`: + +```jsonc +{ + "agent_memory": [ + { + "binding": "MY_MEMORY", + "namespace": "my-namespace" + } + ] +} +``` + +Wrangler will automatically provision the namespace during deployment if it does not already exist. Type generation via `wrangler types` is also supported. From 4be1d272254126d34e549d69f127b52b9eab7836 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Mon, 20 Apr 2026 16:57:36 +0100 Subject: [PATCH 08/29] Fix lint, format, and test type errors - Reformat changeset example JSONC (tabs + trailing commas, per oxfmt). - Mark Plugin and RemoteProxyConnectionString as type-only imports in the miniflare agent-memory plugin to satisfy consistent-type-imports. - Update provision tests to use agent_memory_namespace (the actual metadata binding type) instead of agent_memory. --- .changeset/agent-memory-binding-support.md | 12 ++++++------ packages/miniflare/src/plugins/agent-memory/index.ts | 3 +-- packages/wrangler/src/__tests__/provision.test.ts | 6 +++--- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.changeset/agent-memory-binding-support.md b/.changeset/agent-memory-binding-support.md index 514cd39e4c..a11ef903d6 100644 --- a/.changeset/agent-memory-binding-support.md +++ b/.changeset/agent-memory-binding-support.md @@ -11,12 +11,12 @@ To configure an `agent_memory` binding, add the following to your `wrangler.json ```jsonc { - "agent_memory": [ - { - "binding": "MY_MEMORY", - "namespace": "my-namespace" - } - ] + "agent_memory": [ + { + "binding": "MY_MEMORY", + "namespace": "my-namespace", + }, + ], } ``` diff --git a/packages/miniflare/src/plugins/agent-memory/index.ts b/packages/miniflare/src/plugins/agent-memory/index.ts index de1b34de59..2b5707c11b 100644 --- a/packages/miniflare/src/plugins/agent-memory/index.ts +++ b/packages/miniflare/src/plugins/agent-memory/index.ts @@ -1,11 +1,10 @@ import { z } from "zod"; import { getUserBindingServiceName, - Plugin, ProxyNodeBinding, remoteProxyClientWorker, - RemoteProxyConnectionString, } from "../shared"; +import type { Plugin, RemoteProxyConnectionString } from "../shared"; const AgentMemoryEntrySchema = z.object({ namespace: z.string(), diff --git a/packages/wrangler/src/__tests__/provision.test.ts b/packages/wrangler/src/__tests__/provision.test.ts index 5edfa4ad14..7cb8ffad2e 100644 --- a/packages/wrangler/src/__tests__/provision.test.ts +++ b/packages/wrangler/src/__tests__/provision.test.ts @@ -1334,7 +1334,7 @@ describe("resource provisioning", () => { result: { bindings: [ { - type: "agent_memory", + type: "agent_memory_namespace", name: "MEMORY", namespace: "my-agent-namespace", }, @@ -1364,7 +1364,7 @@ describe("resource provisioning", () => { expectedBindings: [ { name: "MEMORY", - type: "agent_memory", + type: "agent_memory_namespace", namespace: "my-agent-namespace", }, ], @@ -1387,7 +1387,7 @@ describe("resource provisioning", () => { expectedBindings: [ { name: "MEMORY", - type: "agent_memory", + type: "agent_memory_namespace", namespace: "my-agent-namespace", }, ], From ddd56edb41b9f700abc94ed20724cf77f4393f2d Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Mon, 20 Apr 2026 17:53:02 +0100 Subject: [PATCH 09/29] Fix deploy/bindings test to use agent_memory_namespace The upload form emits type "agent_memory_namespace" for agent memory bindings, but the deploy/bindings test was asserting on "agent_memory", causing the toEqual(arrayContaining(...)) check to fail. --- packages/wrangler/src/__tests__/deploy/bindings.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wrangler/src/__tests__/deploy/bindings.test.ts b/packages/wrangler/src/__tests__/deploy/bindings.test.ts index f9677a51fc..bd1007c6dc 100644 --- a/packages/wrangler/src/__tests__/deploy/bindings.test.ts +++ b/packages/wrangler/src/__tests__/deploy/bindings.test.ts @@ -2171,7 +2171,7 @@ describe("deploy", () => { expectedBindings: [ { name: "MEMORY", - type: "agent_memory", + type: "agent_memory_namespace", namespace: "my-agent-namespace", }, ], From 92bdc15130b38ffacf087aacd014d0458f85ddc4 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 21 Apr 2026 13:36:31 +0100 Subject: [PATCH 10/29] Address review feedback for agent_memory binding - Rename emitted ambient type AgentMemory to AgentMemoryNamespace to match the type declared in workerd (cloudflare/workerd#6628) and follow the AiSearchNamespace precedent. - Consolidate the AgentMemoryNamespace interface by importing it from agent-memory/client.ts in agent-memory/provisioning.ts instead of redeclaring a narrower shape there. - Split agent-memory/client.ts into two layers: low-level helpers that take (complianceConfig, accountId, ...) and high-level wrappers that resolve auth via requireAuth. agent-memory/provisioning.ts now delegates to the low-level helpers, removing the duplicated fetch calls. - Reorder the agent_memory block in deploy/config-diffs.ts so it sits alongside the related ai_search blocks. --- .../src/__tests__/type-generation.test.ts | 6 +- packages/wrangler/src/agent-memory/client.ts | 81 +++++++++++++++---- .../wrangler/src/agent-memory/provisioning.ts | 26 ++---- packages/wrangler/src/deploy/config-diffs.ts | 12 +-- .../wrangler/src/type-generation/index.ts | 9 ++- 5 files changed, 87 insertions(+), 47 deletions(-) diff --git a/packages/wrangler/src/__tests__/type-generation.test.ts b/packages/wrangler/src/__tests__/type-generation.test.ts index 263f792b11..1042cfcc25 100644 --- a/packages/wrangler/src/__tests__/type-generation.test.ts +++ b/packages/wrangler/src/__tests__/type-generation.test.ts @@ -794,7 +794,7 @@ describe("generate types - CLI", () => { VPC_SERVICE_BINDING: Fetcher; AI_SEARCH_NS_BINDING: AiSearchNamespace; AI_SEARCH_BINDING: AiSearchInstance; - AGENT_MEMORY_BINDING: AgentMemory; + AGENT_MEMORY_BINDING: AgentMemoryNamespace; LOGFWDR_SCHEMA: any; BROWSER_BINDING: Fetcher; AI_BINDING: Ai; @@ -914,7 +914,7 @@ describe("generate types - CLI", () => { VPC_SERVICE_BINDING: Fetcher; AI_SEARCH_NS_BINDING: AiSearchNamespace; AI_SEARCH_BINDING: AiSearchInstance; - AGENT_MEMORY_BINDING: AgentMemory; + AGENT_MEMORY_BINDING: AgentMemoryNamespace; LOGFWDR_SCHEMA: any; BROWSER_BINDING: Fetcher; AI_BINDING: Ai; @@ -1097,7 +1097,7 @@ describe("generate types - CLI", () => { VPC_SERVICE_BINDING: Fetcher; AI_SEARCH_NS_BINDING: AiSearchNamespace; AI_SEARCH_BINDING: AiSearchInstance; - AGENT_MEMORY_BINDING: AgentMemory; + AGENT_MEMORY_BINDING: AgentMemoryNamespace; LOGFWDR_SCHEMA: any; BROWSER_BINDING: Fetcher; AI_BINDING: Ai; diff --git a/packages/wrangler/src/agent-memory/client.ts b/packages/wrangler/src/agent-memory/client.ts index e03c099264..56da4d48d4 100644 --- a/packages/wrangler/src/agent-memory/client.ts +++ b/packages/wrangler/src/agent-memory/client.ts @@ -1,6 +1,6 @@ import { fetchListResult, fetchResult } from "../cfetch"; import { requireAuth } from "../user"; -import type { Config } from "@cloudflare/workers-utils"; +import type { ComplianceConfig, Config } from "@cloudflare/workers-utils"; export type AgentMemoryNamespace = { id: string; @@ -10,13 +10,22 @@ export type AgentMemoryNamespace = { updated_at: string; }; -export async function createNamespace( - config: Config, +// ============================================================================ +// Low-level request helpers +// +// These take a ComplianceConfig + accountId directly and perform the raw HTTP +// call. They are shared between the high-level command wrappers below and the +// provisioning flow (see agent-memory/provisioning.ts), which already has an +// accountId in hand and cannot call requireAuth. +// ============================================================================ + +export async function createNamespaceRequest( + complianceConfig: ComplianceConfig, + accountId: string, name: string ): Promise { - const accountId = await requireAuth(config); return await fetchResult( - config, + complianceConfig, `/accounts/${accountId}/agentmemory/namespaces`, { method: "POST", @@ -26,35 +35,73 @@ export async function createNamespace( ); } -export async function listNamespaces( - config: Config +export async function listNamespacesRequest( + complianceConfig: ComplianceConfig, + accountId: string ): Promise { - const accountId = await requireAuth(config); return await fetchListResult( - config, + complianceConfig, `/accounts/${accountId}/agentmemory/namespaces` ); } -export async function getNamespace( - config: Config, +export async function getNamespaceRequest( + complianceConfig: ComplianceConfig, + accountId: string, namespaceName: string ): Promise { - const accountId = await requireAuth(config); return await fetchResult( - config, + complianceConfig, `/accounts/${accountId}/agentmemory/namespaces/${namespaceName}` ); } -export async function deleteNamespace( - config: Config, +export async function deleteNamespaceRequest( + complianceConfig: ComplianceConfig, + accountId: string, namespaceName: string ): Promise { - const accountId = await requireAuth(config); await fetchResult( - config, + complianceConfig, `/accounts/${accountId}/agentmemory/namespaces/${namespaceName}`, { method: "DELETE" } ); } + +// ============================================================================ +// High-level command wrappers +// +// Used by the `wrangler agent-memory namespace …` commands. Each resolves the +// account id via requireAuth and delegates to the low-level helper above. +// ============================================================================ + +export async function createNamespace( + config: Config, + name: string +): Promise { + const accountId = await requireAuth(config); + return await createNamespaceRequest(config, accountId, name); +} + +export async function listNamespaces( + config: Config +): Promise { + const accountId = await requireAuth(config); + return await listNamespacesRequest(config, accountId); +} + +export async function getNamespace( + config: Config, + namespaceName: string +): Promise { + const accountId = await requireAuth(config); + return await getNamespaceRequest(config, accountId, namespaceName); +} + +export async function deleteNamespace( + config: Config, + namespaceName: string +): Promise { + const accountId = await requireAuth(config); + await deleteNamespaceRequest(config, accountId, namespaceName); +} diff --git a/packages/wrangler/src/agent-memory/provisioning.ts b/packages/wrangler/src/agent-memory/provisioning.ts index 8b3a48bb85..1c812afaad 100644 --- a/packages/wrangler/src/agent-memory/provisioning.ts +++ b/packages/wrangler/src/agent-memory/provisioning.ts @@ -1,14 +1,10 @@ import { APIError, type ComplianceConfig } from "@cloudflare/workers-utils"; -import { fetchResult } from "../cfetch"; - -export interface AgentMemoryNamespace { - id: string; - name: string; -} +import { createNamespaceRequest, getNamespaceRequest } from "./client"; +import type { AgentMemoryNamespace } from "./client"; /** * Get an Agent Memory namespace for the given account. - * Throws an APIError (status 404) if the namespace does not exist. + * Returns `null` if the namespace does not exist (404); other errors propagate. */ export async function getAgentMemoryNamespace( complianceConfig: ComplianceConfig, @@ -16,10 +12,10 @@ export async function getAgentMemoryNamespace( namespaceName: string ): Promise { try { - return await fetchResult( + return await getNamespaceRequest( complianceConfig, - `/accounts/${accountId}/agentmemory/namespaces/${namespaceName}`, - { method: "GET" } + accountId, + namespaceName ); } catch (e) { if (e instanceof APIError && e.status === 404) { @@ -39,13 +35,5 @@ export async function createAgentMemoryNamespace( accountId: string, namespaceName: string ): Promise { - await fetchResult( - complianceConfig, - `/accounts/${accountId}/agentmemory/namespaces`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ name: namespaceName }), - } - ); + await createNamespaceRequest(complianceConfig, accountId, namespaceName); } diff --git a/packages/wrangler/src/deploy/config-diffs.ts b/packages/wrangler/src/deploy/config-diffs.ts index 0301aa8936..a6b01bf757 100644 --- a/packages/wrangler/src/deploy/config-diffs.ts +++ b/packages/wrangler/src/deploy/config-diffs.ts @@ -213,12 +213,6 @@ function removeRemoteConfigFieldFromBindings(normalizedConfig: Config): void { ); } - if (normalizedConfig.agent_memory?.length) { - normalizedConfig.agent_memory = normalizedConfig.agent_memory.map( - ({ remote: _, ...binding }) => binding - ); - } - if (normalizedConfig.queues?.producers?.length) { normalizedConfig.queues.producers = normalizedConfig.queues.producers.map( ({ remote: _, ...binding }) => binding @@ -244,6 +238,12 @@ function removeRemoteConfigFieldFromBindings(normalizedConfig: Config): void { ); } + if (normalizedConfig.agent_memory?.length) { + normalizedConfig.agent_memory = normalizedConfig.agent_memory.map( + ({ remote: _, ...binding }) => binding + ); + } + if (normalizedConfig.flagship?.length) { normalizedConfig.flagship = normalizedConfig.flagship.map( ({ remote: _, ...binding }) => binding diff --git a/packages/wrangler/src/type-generation/index.ts b/packages/wrangler/src/type-generation/index.ts index 882e0744fe..342bed1c25 100644 --- a/packages/wrangler/src/type-generation/index.ts +++ b/packages/wrangler/src/type-generation/index.ts @@ -2350,7 +2350,12 @@ function collectCoreBindings( }); } - addBinding(agentMemory.binding, "AgentMemory", "agent_memory", envName); + addBinding( + agentMemory.binding, + "AgentMemoryNamespace", + "agent_memory", + envName + ); } // Pipelines handled separately for async schema fetching @@ -3509,7 +3514,7 @@ function collectCoreBindingsPerEnvironment( bindings.push({ bindingCategory: "agent_memory", name: agentMemory.binding, - type: "AgentMemory", + type: "AgentMemoryNamespace", }); } From 2ae9fccb90b4cf2c226486ad8de65c0d38ae1e49 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 21 Apr 2026 14:09:11 +0100 Subject: [PATCH 11/29] Use fetchResult for agent_memory delete Matches the dominant pattern in the wrangler codebase (ai-search.ts, pipelines/client.ts, vectorize/client.ts, etc.); fetchResult was just a style nit. No behavior change. --- packages/wrangler/src/agent-memory/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wrangler/src/agent-memory/client.ts b/packages/wrangler/src/agent-memory/client.ts index 56da4d48d4..6f8f2dae05 100644 --- a/packages/wrangler/src/agent-memory/client.ts +++ b/packages/wrangler/src/agent-memory/client.ts @@ -61,7 +61,7 @@ export async function deleteNamespaceRequest( accountId: string, namespaceName: string ): Promise { - await fetchResult( + await fetchResult( complianceConfig, `/accounts/${accountId}/agentmemory/namespaces/${namespaceName}`, { method: "DELETE" } From ca9d124e92b4bfd5b81974f37a2136c91146f8b1 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 21 Apr 2026 15:12:11 +0100 Subject: [PATCH 12/29] Address more agent_memory review feedback - wrangler agent-memory namespace create: add --json flag and switch the text output to logger.table, matching the list/get commands. - Add TODO on agent-memory/client.ts pointing at the migration to the Cloudflare TypeScript SDK once it exposes /agentmemory endpoints (same situation as ai-search). --- .../src/__tests__/agent-memory.test.ts | 22 +++++++++++++++ packages/wrangler/src/agent-memory/client.ts | 4 +++ packages/wrangler/src/agent-memory/create.ts | 28 ++++++++++++++++--- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/packages/wrangler/src/__tests__/agent-memory.test.ts b/packages/wrangler/src/__tests__/agent-memory.test.ts index 20ddee22da..2fe3150d08 100644 --- a/packages/wrangler/src/__tests__/agent-memory.test.ts +++ b/packages/wrangler/src/__tests__/agent-memory.test.ts @@ -110,6 +110,28 @@ describe("agent-memory namespace commands", () => { expect(std.err).toMatchInlineSnapshot(`""`); }); + it("should create a namespace and output JSON with --json", async ({ + expect, + }) => { + msw.use( + http.post( + "*/accounts/:accountId/agentmemory/namespaces", + async ({ request }) => { + const body = (await request.json()) as { name: string }; + expect(body.name).toBe("my-namespace"); + return HttpResponse.json(createFetchResult(TEST_NAMESPACE, true)); + }, + { once: true } + ) + ); + + await runWrangler("agent-memory namespace create my-namespace --json"); + + const parsed = JSON.parse(std.out); + expect(parsed).toEqual(TEST_NAMESPACE); + expect(std.err).toMatchInlineSnapshot(`""`); + }); + // ── list ────────────────────────────────────────────────────────────────── it("should list namespaces in a table", async ({ expect }) => { diff --git a/packages/wrangler/src/agent-memory/client.ts b/packages/wrangler/src/agent-memory/client.ts index 6f8f2dae05..73eb8d86fb 100644 --- a/packages/wrangler/src/agent-memory/client.ts +++ b/packages/wrangler/src/agent-memory/client.ts @@ -1,3 +1,7 @@ +// TODO(agent-memory): migrate this client to the Cloudflare TypeScript SDK +// once it covers the /agentmemory endpoints. Using `fetchResult` directly for +// now, matching the current precedent for other products that pre-date the +// SDK support (see `src/ai-search/client.ts`). import { fetchListResult, fetchResult } from "../cfetch"; import { requireAuth } from "../user"; import type { ComplianceConfig, Config } from "@cloudflare/workers-utils"; diff --git a/packages/wrangler/src/agent-memory/create.ts b/packages/wrangler/src/agent-memory/create.ts index d4cca8d1bd..0fbdea6aba 100644 --- a/packages/wrangler/src/agent-memory/create.ts +++ b/packages/wrangler/src/agent-memory/create.ts @@ -8,6 +8,9 @@ export const agentMemoryNamespaceCreateCommand = createCommand({ status: "open beta", owner: "Product: Agent Memory", }, + behaviour: { + printBanner: (args) => !args.json, + }, args: { namespace: { type: "string", @@ -15,12 +18,29 @@ export const agentMemoryNamespaceCreateCommand = createCommand({ description: "The name for the new namespace (max 32 characters, alphanumeric with embedded hyphens)", }, + json: { + type: "boolean", + default: false, + description: "Return output as JSON", + }, }, positionalArgs: ["namespace"], - async handler({ namespace }, { config }) { + async handler({ namespace, json }, { config }) { const result = await createNamespace(config, namespace); - logger.log(`✅ Created Agent Memory namespace`); - logger.log(` ID: ${result.id}`); - logger.log(` Name: ${result.name}`); + + if (json) { + logger.json(result); + return; + } + + logger.log(`✅ Created Agent Memory namespace "${result.name}"`); + logger.table([ + { + namespace_id: result.id, + name: result.name, + account_id: result.account_id, + created_at: result.created_at, + }, + ]); }, }); From 35ffa674308fda88f6c7d8c78cd00f7b935edfb1 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 22 Apr 2026 21:26:50 +0100 Subject: [PATCH 13/29] [wrangler] Add agentmemory:write OAuth scope --- .changeset/agent-memory-binding-support.md | 2 ++ packages/wrangler/src/__tests__/whoami.test.ts | 3 +++ packages/wrangler/src/user/user.ts | 2 ++ 3 files changed, 7 insertions(+) diff --git a/.changeset/agent-memory-binding-support.md b/.changeset/agent-memory-binding-support.md index a11ef903d6..63d92dcc79 100644 --- a/.changeset/agent-memory-binding-support.md +++ b/.changeset/agent-memory-binding-support.md @@ -21,3 +21,5 @@ To configure an `agent_memory` binding, add the following to your `wrangler.json ``` Wrangler will automatically provision the namespace during deployment if it does not already exist. Type generation via `wrangler types` is also supported. + +This change also adds the `agentmemory:write` OAuth scope to Wrangler's default login scopes, so `wrangler login` can request the permissions needed to provision and manage Agent Memory namespaces. diff --git a/packages/wrangler/src/__tests__/whoami.test.ts b/packages/wrangler/src/__tests__/whoami.test.ts index 80bc079cef..658dd34870 100644 --- a/packages/wrangler/src/__tests__/whoami.test.ts +++ b/packages/wrangler/src/__tests__/whoami.test.ts @@ -348,6 +348,7 @@ describe("whoami", () => { - ai:write - ai-search:write - ai-search:run + - agentmemory:write - queues:write - pipelines:write - secrets_store:write @@ -422,6 +423,7 @@ describe("whoami", () => { - ai:write - ai-search:write - ai-search:run + - agentmemory:write - queues:write - pipelines:write - secrets_store:write @@ -538,6 +540,7 @@ describe("whoami", () => { - ai:write - ai-search:write - ai-search:run + - agentmemory:write - queues:write - pipelines:write - secrets_store:write diff --git a/packages/wrangler/src/user/user.ts b/packages/wrangler/src/user/user.ts index 1000da6d3e..b1bcd1c85f 100644 --- a/packages/wrangler/src/user/user.ts +++ b/packages/wrangler/src/user/user.ts @@ -372,6 +372,8 @@ const DefaultScopes = { "ai:write": "See and change Workers AI catalog and assets", "ai-search:write": "See and change AI Search data", "ai-search:run": "Run search queries on your AI Search instances", + "agentmemory:write": + "See and change Agent Memory data such as keys and namespaces.", "queues:write": "See and change Cloudflare Queues settings and data", "pipelines:write": "See and change Cloudflare Pipelines configurations and data", From 276958884d526ada4f13b1ea25a27409badeae30 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 22 Apr 2026 21:57:13 +0100 Subject: [PATCH 14/29] [wrangler] Wrap user-caused agent-memory errors as UserError Catch the predictable client-side API errors from the agent-memory namespace commands and rethrow them as UserError with friendly messages so they do not get reported to Sentry: - get/delete on an unknown namespace (404) now explains the namespace was not found and suggests running `namespace list`. - create with an invalid or duplicate name (400/409/422) now surfaces the server's validation message wrapped in a UserError. --- .../src/__tests__/agent-memory.test.ts | 116 ++++++++++++++++++ packages/wrangler/src/agent-memory/create.ts | 28 ++++- packages/wrangler/src/agent-memory/delete.ts | 13 +- packages/wrangler/src/agent-memory/get.ts | 14 ++- 4 files changed, 168 insertions(+), 3 deletions(-) diff --git a/packages/wrangler/src/__tests__/agent-memory.test.ts b/packages/wrangler/src/__tests__/agent-memory.test.ts index 2fe3150d08..eb9a5d3edf 100644 --- a/packages/wrangler/src/__tests__/agent-memory.test.ts +++ b/packages/wrangler/src/__tests__/agent-memory.test.ts @@ -1,3 +1,4 @@ +import { UserError } from "@cloudflare/workers-utils"; import { http, HttpResponse } from "msw"; import { afterEach, describe, it } from "vitest"; import { endEventLoop } from "./helpers/end-event-loop"; @@ -305,4 +306,119 @@ describe("agent-memory namespace commands", () => { expect(std.out).toContain(`✅ Deleted Agent Memory namespace`); expect(std.err).toMatchInlineSnapshot(`""`); }); + + // ── user-caused errors ──────────────────────────────────────────────────── + // These are surfaced as UserErrors so they are not reported to Sentry. + + it("should throw a UserError when `get` hits a 404", async ({ expect }) => { + msw.use( + http.get( + `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.name}`, + () => { + return HttpResponse.json( + createFetchResult(null, false, [ + { code: 1000, message: "namespace not found" }, + ]), + { status: 404 } + ); + }, + { once: true } + ) + ); + + await expect( + runWrangler(`agent-memory namespace get ${TEST_NAMESPACE.name}`) + ).rejects.toThrow(UserError); + expect(std.err).toContain( + `Agent Memory namespace "${TEST_NAMESPACE.name}" not found` + ); + }); + + it("should throw a UserError when `delete` hits a 404", async ({ + expect, + }) => { + msw.use( + http.delete( + `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.name}`, + () => { + return HttpResponse.json( + createFetchResult(null, false, [ + { code: 1000, message: "namespace not found" }, + ]), + { status: 404 } + ); + }, + { once: true } + ) + ); + + await expect( + runWrangler( + `agent-memory namespace delete ${TEST_NAMESPACE.name} --force` + ) + ).rejects.toThrow(UserError); + expect(std.err).toContain( + `Agent Memory namespace "${TEST_NAMESPACE.name}" not found` + ); + }); + + it("should throw a UserError when `create` receives a 400 (invalid name)", async ({ + expect, + }) => { + msw.use( + http.post( + "*/accounts/:accountId/agentmemory/namespaces", + () => { + return HttpResponse.json( + createFetchResult(null, false, [ + { + code: 1001, + message: + "namespace name must be 1-32 characters, alphanumeric with embedded hyphens", + }, + ]), + { status: 400 } + ); + }, + { once: true } + ) + ); + + await expect( + runWrangler("agent-memory namespace create bad_name!") + ).rejects.toThrow(UserError); + expect(std.err).toContain( + `Failed to create Agent Memory namespace "bad_name!"` + ); + expect(std.err).toContain( + "namespace name must be 1-32 characters, alphanumeric with embedded hyphens" + ); + }); + + it("should throw a UserError when `create` receives a 409 (duplicate)", async ({ + expect, + }) => { + msw.use( + http.post( + "*/accounts/:accountId/agentmemory/namespaces", + () => { + return HttpResponse.json( + createFetchResult(null, false, [ + { code: 1002, message: "namespace already exists" }, + ]), + { status: 409 } + ); + }, + { once: true } + ) + ); + + await expect( + runWrangler(`agent-memory namespace create ${TEST_NAMESPACE.name}`) + ).rejects.toThrow(UserError); + expect(std.err).toContain( + `Failed to create Agent Memory namespace "${TEST_NAMESPACE.name}"` + ); + expect(std.err).toContain("namespace already exists"); + }); }); diff --git a/packages/wrangler/src/agent-memory/create.ts b/packages/wrangler/src/agent-memory/create.ts index 0fbdea6aba..c46f2485e3 100644 --- a/packages/wrangler/src/agent-memory/create.ts +++ b/packages/wrangler/src/agent-memory/create.ts @@ -1,3 +1,4 @@ +import { APIError, UserError } from "@cloudflare/workers-utils"; import { createCommand } from "../core/create-command"; import { logger } from "../logger"; import { createNamespace } from "./client"; @@ -26,7 +27,32 @@ export const agentMemoryNamespaceCreateCommand = createCommand({ }, positionalArgs: ["namespace"], async handler({ namespace, json }, { config }) { - const result = await createNamespace(config, namespace); + let result; + try { + result = await createNamespace(config, namespace); + } catch (e) { + // Surface server-side validation / conflict errors (e.g. invalid name, + // duplicate namespace) as UserErrors so they are not reported to Sentry + // and the user sees the underlying message from the API. + if ( + e instanceof APIError && + e.status !== undefined && + [400, 409, 422].includes(e.status) + ) { + const details = e.notes + .map((n) => n.text) + .filter((t) => t.length > 0) + .join("\n"); + throw new UserError( + `Failed to create Agent Memory namespace "${namespace}".${details ? `\n${details}` : ""}`, + { + telemetryMessage: + "Agent Memory namespace create failed with client error", + } + ); + } + throw e; + } if (json) { logger.json(result); diff --git a/packages/wrangler/src/agent-memory/delete.ts b/packages/wrangler/src/agent-memory/delete.ts index 49d4ac543e..0038ab37e8 100644 --- a/packages/wrangler/src/agent-memory/delete.ts +++ b/packages/wrangler/src/agent-memory/delete.ts @@ -1,3 +1,4 @@ +import { APIError, UserError } from "@cloudflare/workers-utils"; import { createCommand } from "../core/create-command"; import { confirm } from "../dialogs"; import { logger } from "../logger"; @@ -34,7 +35,17 @@ export const agentMemoryNamespaceDeleteCommand = createCommand({ } } - await deleteNamespace(config, namespace_name); + try { + await deleteNamespace(config, namespace_name); + } catch (e) { + if (e instanceof APIError && e.status === 404) { + throw new UserError( + `Agent Memory namespace "${namespace_name}" not found. Use 'wrangler agent-memory namespace list' to see available namespaces.`, + { telemetryMessage: "Agent Memory namespace not found" } + ); + } + throw e; + } logger.log(`✅ Deleted Agent Memory namespace ${namespace_name}`); }, }); diff --git a/packages/wrangler/src/agent-memory/get.ts b/packages/wrangler/src/agent-memory/get.ts index e51fd66786..e108d4bfba 100644 --- a/packages/wrangler/src/agent-memory/get.ts +++ b/packages/wrangler/src/agent-memory/get.ts @@ -1,3 +1,4 @@ +import { APIError, UserError } from "@cloudflare/workers-utils"; import { createCommand } from "../core/create-command"; import { logger } from "../logger"; import { getNamespace } from "./client"; @@ -25,7 +26,18 @@ export const agentMemoryNamespaceGetCommand = createCommand({ }, positionalArgs: ["namespace_name"], async handler({ namespace_name, json }, { config }) { - const ns = await getNamespace(config, namespace_name); + let ns; + try { + ns = await getNamespace(config, namespace_name); + } catch (e) { + if (e instanceof APIError && e.status === 404) { + throw new UserError( + `Agent Memory namespace "${namespace_name}" not found. Use 'wrangler agent-memory namespace list' to see available namespaces.`, + { telemetryMessage: "Agent Memory namespace not found" } + ); + } + throw e; + } if (json) { logger.json(ns); From 7ff5bef2ee7db6cdc134cc01d59c96c2d262caf0 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 5 May 2026 10:22:11 +0100 Subject: [PATCH 15/29] fix the OAuth scope --- .changeset/agent-memory-binding-support.md | 2 +- packages/wrangler/src/__tests__/deploy/core.test.ts | 4 ++-- packages/wrangler/src/__tests__/user.test.ts | 12 ++++++------ packages/wrangler/src/user/user.ts | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.changeset/agent-memory-binding-support.md b/.changeset/agent-memory-binding-support.md index 63d92dcc79..bf34802ced 100644 --- a/.changeset/agent-memory-binding-support.md +++ b/.changeset/agent-memory-binding-support.md @@ -22,4 +22,4 @@ To configure an `agent_memory` binding, add the following to your `wrangler.json Wrangler will automatically provision the namespace during deployment if it does not already exist. Type generation via `wrangler types` is also supported. -This change also adds the `agentmemory:write` OAuth scope to Wrangler's default login scopes, so `wrangler login` can request the permissions needed to provision and manage Agent Memory namespaces. +This change also adds the `agent-memory:write` OAuth scope to Wrangler's default login scopes, so `wrangler login` can request the permissions needed to provision and manage Agent Memory namespaces. diff --git a/packages/wrangler/src/__tests__/deploy/core.test.ts b/packages/wrangler/src/__tests__/deploy/core.test.ts index eeae023770..4f9a2274bd 100644 --- a/packages/wrangler/src/__tests__/deploy/core.test.ts +++ b/packages/wrangler/src/__tests__/deploy/core.test.ts @@ -761,7 +761,7 @@ describe("deploy", () => { ⛅️ wrangler x.x.x ────────────────── Attempting to login via OAuth... - Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 + Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20agent-memory%3Awrite%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in. Total Upload: xx KiB / gzip: xx KiB Worker Startup Time: 100 ms @@ -807,7 +807,7 @@ describe("deploy", () => { ⛅️ wrangler x.x.x ────────────────── Attempting to login via OAuth... - Opening a link in your default browser: https://dash.staging.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 + Opening a link in your default browser: https://dash.staging.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20agent-memory%3Awrite%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in. Total Upload: xx KiB / gzip: xx KiB Worker Startup Time: 100 ms diff --git a/packages/wrangler/src/__tests__/user.test.ts b/packages/wrangler/src/__tests__/user.test.ts index e6a41a167a..401179750d 100644 --- a/packages/wrangler/src/__tests__/user.test.ts +++ b/packages/wrangler/src/__tests__/user.test.ts @@ -85,7 +85,7 @@ describe("User", () => { ⛅️ wrangler x.x.x ────────────────── Attempting to login via OAuth... - Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 + Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20agent-memory%3Awrite%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in." `); expect(readAuthConfigFile()).toEqual({ @@ -131,7 +131,7 @@ describe("User", () => { Temporary login server listening on 0.0.0.0:8976 Note that the OAuth login page will always redirect to \`localhost:8976\`. If you have changed the callback host or port because you are running in a container, then ensure that you have port forwarding set up correctly. - Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 + Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20agent-memory%3Awrite%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in." `); expect(readAuthConfigFile()).toEqual({ @@ -177,7 +177,7 @@ describe("User", () => { Temporary login server listening on mylocalhost.local:8976 Note that the OAuth login page will always redirect to \`localhost:8976\`. If you have changed the callback host or port because you are running in a container, then ensure that you have port forwarding set up correctly. - Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 + Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20agent-memory%3Awrite%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in." `); expect(readAuthConfigFile()).toEqual({ @@ -223,7 +223,7 @@ describe("User", () => { Temporary login server listening on localhost:8787 Note that the OAuth login page will always redirect to \`localhost:8976\`. If you have changed the callback host or port because you are running in a container, then ensure that you have port forwarding set up correctly. - Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 + Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20agent-memory%3Awrite%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in." `); expect(readAuthConfigFile()).toEqual({ @@ -265,7 +265,7 @@ describe("User", () => { ⛅️ wrangler x.x.x ────────────────── Attempting to login via OAuth... - Opening a link in your default browser: https://dash.staging.cloudflare.com/oauth2/auth?response_type=code&client_id=4b2ea6cc-9421-4761-874b-ce550e0e3def&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 + Opening a link in your default browser: https://dash.staging.cloudflare.com/oauth2/auth?response_type=code&client_id=4b2ea6cc-9421-4761-874b-ce550e0e3def&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20agent-memory%3Awrite%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in." `); @@ -483,7 +483,7 @@ describe("User", () => { ⛅️ wrangler x.x.x ────────────────── Attempting to login via OAuth... - Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 + Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20agent-memory%3Awrite%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in." `); expect(std.warn).toMatchInlineSnapshot(`""`); diff --git a/packages/wrangler/src/user/user.ts b/packages/wrangler/src/user/user.ts index b1bcd1c85f..2688dd0c91 100644 --- a/packages/wrangler/src/user/user.ts +++ b/packages/wrangler/src/user/user.ts @@ -372,7 +372,7 @@ const DefaultScopes = { "ai:write": "See and change Workers AI catalog and assets", "ai-search:write": "See and change AI Search data", "ai-search:run": "Run search queries on your AI Search instances", - "agentmemory:write": + "agent-memory:write": "See and change Agent Memory data such as keys and namespaces.", "queues:write": "See and change Cloudflare Queues settings and data", "pipelines:write": From 4e82c661e38de21ea314415eb9a50869aedabe23 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 5 May 2026 10:35:04 +0100 Subject: [PATCH 16/29] `agent_memory_namespace` is actually just `agent_memory` now --- .../workers-utils/src/config/validation.ts | 4 ++-- .../src/map-worker-metadata-bindings.ts | 2 +- packages/workers-utils/src/types.ts | 4 ++-- .../miniflare-remote-resources.test.ts | 2 +- .../bindings.test.ts | 20 ++++++++--------- .../src/__tests__/deploy/bindings.test.ts | 2 +- .../src/__tests__/print-bindings.test.ts | 2 +- .../wrangler/src/__tests__/provision.test.ts | 6 ++--- .../wrangler/src/__tests__/whoami.test.ts | 6 ++--- .../wrangler/src/api/startDevWorker/utils.ts | 8 +++---- .../src/deployment-bundle/bindings.ts | 22 +++++++++---------- .../create-worker-upload-form.ts | 7 ++---- packages/wrangler/src/dev/miniflare/index.ts | 7 ++---- packages/wrangler/src/utils/print-bindings.ts | 7 ++---- 14 files changed, 44 insertions(+), 55 deletions(-) diff --git a/packages/workers-utils/src/config/validation.ts b/packages/workers-utils/src/config/validation.ts index 02147f8d22..71246c2623 100644 --- a/packages/workers-utils/src/config/validation.ts +++ b/packages/workers-utils/src/config/validation.ts @@ -183,7 +183,7 @@ const bindingTypeFriendlyNames: Record = { vectorize: "Vectorize Index", ai_search_namespace: "AI Search Namespace", ai_search: "AI Search Instance", - agent_memory_namespace: "Agent Memory", + agent_memory: "Agent Memory", hyperdrive: "Hyperdrive Config", service: "Worker", fetcher: "Service Binding", @@ -3037,7 +3037,7 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => { "ai", "ai_search_namespace", "ai_search", - "agent_memory_namespace", + "agent_memory", "kv_namespace", "durable_object_namespace", "d1_database", diff --git a/packages/workers-utils/src/map-worker-metadata-bindings.ts b/packages/workers-utils/src/map-worker-metadata-bindings.ts index a830e08198..a90af6bc59 100644 --- a/packages/workers-utils/src/map-worker-metadata-bindings.ts +++ b/packages/workers-utils/src/map-worker-metadata-bindings.ts @@ -299,7 +299,7 @@ export function mapWorkerMetadataBindings( }, ]; break; - case "agent_memory_namespace": { + case "agent_memory": { configObj.agent_memory = [ ...(configObj.agent_memory ?? []), { diff --git a/packages/workers-utils/src/types.ts b/packages/workers-utils/src/types.ts index 877219c614..99d37ddfa0 100644 --- a/packages/workers-utils/src/types.ts +++ b/packages/workers-utils/src/types.ts @@ -74,7 +74,7 @@ export type WorkerMetadataBinding = | { type: "data_blob"; name: string; part: string } | { type: "ai_search_namespace"; name: string; namespace: string } | { type: "ai_search"; name: string; instance_name: string } - | { type: "agent_memory_namespace"; name: string; namespace: string } + | { type: "agent_memory"; name: string; namespace: string } | { type: "kv_namespace"; name: string; namespace_id: string; raw?: boolean } | { type: "media"; name: string } | { @@ -336,7 +336,7 @@ export type Binding = | ({ type: "vectorize" } & BindingOmit) | ({ type: "ai_search_namespace" } & BindingOmit) | ({ type: "ai_search" } & BindingOmit) - | ({ type: "agent_memory_namespace" } & BindingOmit) + | ({ type: "agent_memory" } & BindingOmit) | ({ type: "hyperdrive" } & BindingOmit) | ({ type: "service" } & BindingOmit) | { type: "fetcher"; fetcher: ServiceFetch } diff --git a/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts b/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts index c04e030d22..15205f107e 100644 --- a/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts +++ b/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts @@ -493,7 +493,7 @@ const testCases: TestCase[] = [ remoteProxySessionConfig: { bindings: { MEMORY: { - type: "agent_memory_namespace", + type: "agent_memory", namespace, }, }, diff --git a/packages/wrangler/src/__tests__/create-worker-upload-form/bindings.test.ts b/packages/wrangler/src/__tests__/create-worker-upload-form/bindings.test.ts index 2387272f0c..412fcfd657 100644 --- a/packages/wrangler/src/__tests__/create-worker-upload-form/bindings.test.ts +++ b/packages/wrangler/src/__tests__/create-worker-upload-form/bindings.test.ts @@ -301,7 +301,7 @@ describe("createWorkerUploadForm — bindings", () => { instance_name: "cloudflare-blog", }, { - type: "agent_memory_namespace" as const, + type: "agent_memory" as const, namespace: "my-agent", }, { type: "inherit" as const }, @@ -377,40 +377,38 @@ describe("createWorkerUploadForm — bindings", () => { }); }); - describe("agent_memory_namespace bindings", () => { - it("should include agent_memory_namespace binding with namespace", ({ - expect, - }) => { + describe("agent_memory bindings", () => { + it("should include agent_memory binding with namespace", ({ expect }) => { const bindings: StartDevWorkerInput["bindings"] = { MEMORY: { - type: "agent_memory_namespace", + type: "agent_memory", namespace: "my-agent", }, }; const form = createWorkerUploadForm(createEsmWorker(), bindings); expect(getBindings(form)).toContainEqual({ name: "MEMORY", - type: "agent_memory_namespace", + type: "agent_memory", namespace: "my-agent", }); }); - it("should throw when agent_memory_namespace has no namespace and not in dry run", ({ + it("should throw when agent_memory has no namespace and not in dry run", ({ expect, }) => { const bindings: StartDevWorkerInput["bindings"] = { - MEMORY: { type: "agent_memory_namespace" } as never, + MEMORY: { type: "agent_memory" } as never, }; expect(() => createWorkerUploadForm(createEsmWorker(), bindings) ).toThrowError('MEMORY bindings must have a "namespace" field'); }); - it("should convert agent_memory_namespace to inherit binding during dry run when namespace is missing", ({ + it("should convert agent_memory to inherit binding during dry run when namespace is missing", ({ expect, }) => { const bindings: StartDevWorkerInput["bindings"] = { - MEMORY: { type: "agent_memory_namespace" } as never, + MEMORY: { type: "agent_memory" } as never, }; const form = createWorkerUploadForm(createEsmWorker(), bindings, { dryRun: true, diff --git a/packages/wrangler/src/__tests__/deploy/bindings.test.ts b/packages/wrangler/src/__tests__/deploy/bindings.test.ts index bd1007c6dc..f9677a51fc 100644 --- a/packages/wrangler/src/__tests__/deploy/bindings.test.ts +++ b/packages/wrangler/src/__tests__/deploy/bindings.test.ts @@ -2171,7 +2171,7 @@ describe("deploy", () => { expectedBindings: [ { name: "MEMORY", - type: "agent_memory_namespace", + type: "agent_memory", namespace: "my-agent-namespace", }, ], diff --git a/packages/wrangler/src/__tests__/print-bindings.test.ts b/packages/wrangler/src/__tests__/print-bindings.test.ts index 075ff8cdf4..c66afb6d81 100644 --- a/packages/wrangler/src/__tests__/print-bindings.test.ts +++ b/packages/wrangler/src/__tests__/print-bindings.test.ts @@ -118,7 +118,7 @@ describe("printBindings — AI Search bindings", () => { it("shows Agent Memory bindings", ({ expect }) => { const output = callPrintBindings({ MEMORY: { - type: "agent_memory_namespace", + type: "agent_memory", namespace: "my-agent", }, }); diff --git a/packages/wrangler/src/__tests__/provision.test.ts b/packages/wrangler/src/__tests__/provision.test.ts index 7cb8ffad2e..5edfa4ad14 100644 --- a/packages/wrangler/src/__tests__/provision.test.ts +++ b/packages/wrangler/src/__tests__/provision.test.ts @@ -1334,7 +1334,7 @@ describe("resource provisioning", () => { result: { bindings: [ { - type: "agent_memory_namespace", + type: "agent_memory", name: "MEMORY", namespace: "my-agent-namespace", }, @@ -1364,7 +1364,7 @@ describe("resource provisioning", () => { expectedBindings: [ { name: "MEMORY", - type: "agent_memory_namespace", + type: "agent_memory", namespace: "my-agent-namespace", }, ], @@ -1387,7 +1387,7 @@ describe("resource provisioning", () => { expectedBindings: [ { name: "MEMORY", - type: "agent_memory_namespace", + type: "agent_memory", namespace: "my-agent-namespace", }, ], diff --git a/packages/wrangler/src/__tests__/whoami.test.ts b/packages/wrangler/src/__tests__/whoami.test.ts index 658dd34870..9b007d149d 100644 --- a/packages/wrangler/src/__tests__/whoami.test.ts +++ b/packages/wrangler/src/__tests__/whoami.test.ts @@ -348,7 +348,7 @@ describe("whoami", () => { - ai:write - ai-search:write - ai-search:run - - agentmemory:write + - agent-memory:write - queues:write - pipelines:write - secrets_store:write @@ -423,7 +423,7 @@ describe("whoami", () => { - ai:write - ai-search:write - ai-search:run - - agentmemory:write + - agent-memory:write - queues:write - pipelines:write - secrets_store:write @@ -540,7 +540,7 @@ describe("whoami", () => { - ai:write - ai-search:write - ai-search:run - - agentmemory:write + - agent-memory:write - queues:write - pipelines:write - secrets_store:write diff --git a/packages/wrangler/src/api/startDevWorker/utils.ts b/packages/wrangler/src/api/startDevWorker/utils.ts index 55b92ecb02..62fbdf92a6 100644 --- a/packages/wrangler/src/api/startDevWorker/utils.ts +++ b/packages/wrangler/src/api/startDevWorker/utils.ts @@ -322,7 +322,7 @@ export function convertConfigToBindings( } case "agent_memory": { for (const { binding, ...x } of info) { - output[binding] = { type: "agent_memory_namespace", ...x }; + output[binding] = { type: "agent_memory", ...x }; } break; } @@ -668,13 +668,13 @@ export function convertWorkerMetadataBindingsToFlatBindings( }; break; } - case "agent_memory_namespace": { + case "agent_memory": { const b = binding as Extract< WorkerMetadataBinding, - { type: "agent_memory_namespace" } + { type: "agent_memory" } >; output[name] = { - type: "agent_memory_namespace", + type: "agent_memory", namespace: b.namespace, }; break; diff --git a/packages/wrangler/src/deployment-bundle/bindings.ts b/packages/wrangler/src/deployment-bundle/bindings.ts index 62a7913721..cca5ec9935 100644 --- a/packages/wrangler/src/deployment-bundle/bindings.ts +++ b/packages/wrangler/src/deployment-bundle/bindings.ts @@ -282,8 +282,8 @@ class AISearchNamespaceHandler extends ProvisionResourceHandler< } class AgentMemoryNamespaceHandler extends ProvisionResourceHandler< - "agent_memory_namespace", - Extract + "agent_memory", + Extract > { get name(): string | undefined { return this.binding.namespace as string; @@ -300,12 +300,12 @@ class AgentMemoryNamespaceHandler extends ProvisionResourceHandler< constructor( bindingName: string, - binding: Extract, + binding: Extract, complianceConfig: ComplianceConfig, accountId: string ) { super( - "agent_memory_namespace", + "agent_memory", bindingName, binding, "namespace", @@ -469,7 +469,7 @@ type ProvisionableBinding = | Extract | Extract | Extract - | Extract; + | Extract; const HANDLERS = { kv_namespace: { @@ -569,7 +569,7 @@ const HANDLERS = { }; }, }, - agent_memory_namespace: { + agent_memory: { Handler: AgentMemoryNamespaceHandler, sort: 4, name: "Agent Memory", @@ -582,7 +582,7 @@ const HANDLERS = { }, toConfig: ( bindingName: string, - binding: Extract + binding: Extract ): CfAgentMemory => { const { type: _, ...rest } = binding; return { @@ -600,7 +600,7 @@ type PendingResource = { | "d1" | "r2_bucket" | "ai_search_namespace" - | "agent_memory_namespace"; + | "agent_memory"; handler: | KVHandler | D1Handler @@ -640,7 +640,7 @@ function createHandler( complianceConfig, accountId ); - case "agent_memory_namespace": + case "agent_memory": return new AgentMemoryNamespaceHandler( bindingName, binding, @@ -668,8 +668,8 @@ function toConfigBinding( return HANDLERS.r2_bucket.toConfig(bindingName, binding); case "ai_search_namespace": return HANDLERS.ai_search_namespace.toConfig(bindingName, binding); - case "agent_memory_namespace": - return HANDLERS.agent_memory_namespace.toConfig(bindingName, binding); + case "agent_memory": + return HANDLERS.agent_memory.toConfig(bindingName, binding); } } diff --git a/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts b/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts index 86f15e0c69..8da98dff70 100644 --- a/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts +++ b/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts @@ -131,10 +131,7 @@ export function createWorkerUploadForm( bindings ); const ai_search = extractBindingsOfType("ai_search", bindings); - const agent_memory = extractBindingsOfType( - "agent_memory_namespace", - bindings - ); + const agent_memory = extractBindingsOfType("agent_memory", bindings); const hyperdrive = extractBindingsOfType("hyperdrive", bindings); const secrets_store_secrets = extractBindingsOfType( "secrets_store_secret", @@ -387,7 +384,7 @@ export function createWorkerUploadForm( } else { metadataBindings.push({ name: binding, - type: "agent_memory_namespace", + type: "agent_memory", namespace, }); } diff --git a/packages/wrangler/src/dev/miniflare/index.ts b/packages/wrangler/src/dev/miniflare/index.ts index c26cc7c940..fa4437eb63 100644 --- a/packages/wrangler/src/dev/miniflare/index.ts +++ b/packages/wrangler/src/dev/miniflare/index.ts @@ -524,10 +524,7 @@ export function buildMiniflareBindingOptions( bindings ); const aiSearchInstanceBindings = extractBindingsOfType("ai_search", bindings); - const agentMemoryBindings = extractBindingsOfType( - "agent_memory_namespace", - bindings - ); + const agentMemoryBindings = extractBindingsOfType("agent_memory", bindings); const imagesBindings = extractBindingsOfType("images", bindings); const mediaBindings = extractBindingsOfType("media", bindings); const browserBindings = extractBindingsOfType("browser", bindings); @@ -637,7 +634,7 @@ export function buildMiniflareBindingOptions( } for (const memory of agentMemoryBindings) { - warnOrError("agent_memory_namespace", memory.remote, "always-remote"); + warnOrError("agent_memory", memory.remote, "always-remote"); } for (const media of mediaBindings) { diff --git a/packages/wrangler/src/utils/print-bindings.ts b/packages/wrangler/src/utils/print-bindings.ts index f97e76f6b0..569cb76c5d 100644 --- a/packages/wrangler/src/utils/print-bindings.ts +++ b/packages/wrangler/src/utils/print-bindings.ts @@ -88,10 +88,7 @@ export function printBindings( bindings ); const ai_search = extractBindingsOfType("ai_search", bindings); - const agent_memory = extractBindingsOfType( - "agent_memory_namespace", - bindings - ); + const agent_memory = extractBindingsOfType("agent_memory", bindings); const hyperdrive = extractBindingsOfType("hyperdrive", bindings); const r2_buckets = extractBindingsOfType("r2_bucket", bindings); const logfwdr = extractBindingsOfType("logfwdr", bindings); @@ -361,7 +358,7 @@ export function printBindings( output.push( ...agent_memory.map(({ binding, namespace }) => ({ name: binding, - type: getBindingTypeFriendlyName("agent_memory_namespace"), + type: getBindingTypeFriendlyName("agent_memory"), value: namespace ?? undefined, mode: getMode({ isSimulatedLocally: false }), })) From 935136fb1cdff45e7ab43d20270881d2b2158171 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 5 May 2026 16:30:31 +0100 Subject: [PATCH 17/29] fix type for new UserError --- .../src/deployment-bundle/create-worker-upload-form.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts b/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts index 8da98dff70..180e7d74d9 100644 --- a/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts +++ b/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts @@ -373,7 +373,9 @@ export function createWorkerUploadForm( namespace ??= INHERIT_SYMBOL; } if (namespace === undefined) { - throw new UserError(`${binding} bindings must have a "namespace" field`); + throw new UserError(`${binding} bindings must have a "namespace" field`, { + telemetryMessage: false, + }); } if (namespace === INHERIT_SYMBOL) { From 0225cce4413c3282b68c118d9e68bf54ec635042 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 5 May 2026 17:10:50 +0100 Subject: [PATCH 18/29] fix API paths --- .../src/__tests__/agent-memory.test.ts | 26 +++++++++---------- .../src/__tests__/deploy/bindings.test.ts | 2 +- .../wrangler/src/__tests__/provision.test.ts | 4 +-- packages/wrangler/src/agent-memory/client.ts | 8 +++--- tools/e2e/common.ts | 4 +-- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/wrangler/src/__tests__/agent-memory.test.ts b/packages/wrangler/src/__tests__/agent-memory.test.ts index eb9a5d3edf..ab55513ffc 100644 --- a/packages/wrangler/src/__tests__/agent-memory.test.ts +++ b/packages/wrangler/src/__tests__/agent-memory.test.ts @@ -93,7 +93,7 @@ describe("agent-memory namespace commands", () => { it("should create a namespace", async ({ expect }) => { msw.use( http.post( - "*/accounts/:accountId/agentmemory/namespaces", + "*/accounts/:accountId/agent-memory/namespaces", async ({ request }) => { const body = (await request.json()) as { name: string }; expect(body.name).toBe("my-namespace"); @@ -116,7 +116,7 @@ describe("agent-memory namespace commands", () => { }) => { msw.use( http.post( - "*/accounts/:accountId/agentmemory/namespaces", + "*/accounts/:accountId/agent-memory/namespaces", async ({ request }) => { const body = (await request.json()) as { name: string }; expect(body.name).toBe("my-namespace"); @@ -138,7 +138,7 @@ describe("agent-memory namespace commands", () => { it("should list namespaces in a table", async ({ expect }) => { msw.use( http.get( - "*/accounts/:accountId/agentmemory/namespaces", + "*/accounts/:accountId/agent-memory/namespaces", () => { return HttpResponse.json( createFetchResult([TEST_NAMESPACE], true, [], [], { @@ -162,7 +162,7 @@ describe("agent-memory namespace commands", () => { it("should list namespaces as JSON with --json", async ({ expect }) => { msw.use( http.get( - "*/accounts/:accountId/agentmemory/namespaces", + "*/accounts/:accountId/agent-memory/namespaces", () => { return HttpResponse.json( createFetchResult([TEST_NAMESPACE], true, [], [], { @@ -186,7 +186,7 @@ describe("agent-memory namespace commands", () => { it("should print a message when no namespaces exist", async ({ expect }) => { msw.use( http.get( - "*/accounts/:accountId/agentmemory/namespaces", + "*/accounts/:accountId/agent-memory/namespaces", () => { return HttpResponse.json( createFetchResult([], true, [], [], { @@ -211,7 +211,7 @@ describe("agent-memory namespace commands", () => { it("should get a namespace in a table", async ({ expect }) => { msw.use( http.get( - `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.name}`, + `*/accounts/:accountId/agent-memory/namespaces/${TEST_NAMESPACE.name}`, () => { return HttpResponse.json(createFetchResult(TEST_NAMESPACE, true)); }, @@ -229,7 +229,7 @@ describe("agent-memory namespace commands", () => { it("should get a namespace as JSON with --json", async ({ expect }) => { msw.use( http.get( - `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.name}`, + `*/accounts/:accountId/agent-memory/namespaces/${TEST_NAMESPACE.name}`, () => { return HttpResponse.json(createFetchResult(TEST_NAMESPACE, true)); }, @@ -257,7 +257,7 @@ describe("agent-memory namespace commands", () => { msw.use( http.delete( - `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.name}`, + `*/accounts/:accountId/agent-memory/namespaces/${TEST_NAMESPACE.name}`, () => { return HttpResponse.json(createFetchResult(null, true)); }, @@ -291,7 +291,7 @@ describe("agent-memory namespace commands", () => { }) => { msw.use( http.delete( - `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.name}`, + `*/accounts/:accountId/agent-memory/namespaces/${TEST_NAMESPACE.name}`, () => { return HttpResponse.json(createFetchResult(null, true)); }, @@ -313,7 +313,7 @@ describe("agent-memory namespace commands", () => { it("should throw a UserError when `get` hits a 404", async ({ expect }) => { msw.use( http.get( - `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.name}`, + `*/accounts/:accountId/agent-memory/namespaces/${TEST_NAMESPACE.name}`, () => { return HttpResponse.json( createFetchResult(null, false, [ @@ -339,7 +339,7 @@ describe("agent-memory namespace commands", () => { }) => { msw.use( http.delete( - `*/accounts/:accountId/agentmemory/namespaces/${TEST_NAMESPACE.name}`, + `*/accounts/:accountId/agent-memory/namespaces/${TEST_NAMESPACE.name}`, () => { return HttpResponse.json( createFetchResult(null, false, [ @@ -367,7 +367,7 @@ describe("agent-memory namespace commands", () => { }) => { msw.use( http.post( - "*/accounts/:accountId/agentmemory/namespaces", + "*/accounts/:accountId/agent-memory/namespaces", () => { return HttpResponse.json( createFetchResult(null, false, [ @@ -400,7 +400,7 @@ describe("agent-memory namespace commands", () => { }) => { msw.use( http.post( - "*/accounts/:accountId/agentmemory/namespaces", + "*/accounts/:accountId/agent-memory/namespaces", () => { return HttpResponse.json( createFetchResult(null, false, [ diff --git a/packages/wrangler/src/__tests__/deploy/bindings.test.ts b/packages/wrangler/src/__tests__/deploy/bindings.test.ts index f9677a51fc..442a342247 100644 --- a/packages/wrangler/src/__tests__/deploy/bindings.test.ts +++ b/packages/wrangler/src/__tests__/deploy/bindings.test.ts @@ -86,7 +86,7 @@ describe("deploy", () => { // Pretend all Agent Memory namespaces exist for the same reason. msw.use( http.get( - "*/accounts/:accountId/agentmemory/namespaces/:namespaceName", + "*/accounts/:accountId/agent-memory/namespaces/:namespaceName", async () => { return HttpResponse.json(createFetchResult({})); } diff --git a/packages/wrangler/src/__tests__/provision.test.ts b/packages/wrangler/src/__tests__/provision.test.ts index 5edfa4ad14..650e8839e6 100644 --- a/packages/wrangler/src/__tests__/provision.test.ts +++ b/packages/wrangler/src/__tests__/provision.test.ts @@ -1521,7 +1521,7 @@ function mockGetAgentMemoryNamespace( ) { msw.use( http.get( - "*/accounts/:accountId/agentmemory/namespaces/:namespaceName", + "*/accounts/:accountId/agent-memory/namespaces/:namespaceName", async ({ params }) => { expect(params.namespaceName).toEqual(namespaceName); if (missing) { @@ -1550,7 +1550,7 @@ function mockCreateAgentMemoryNamespace( ) { msw.use( http.post( - "*/accounts/:accountId/agentmemory/namespaces", + "*/accounts/:accountId/agent-memory/namespaces", async ({ request }) => { if (options.assertName) { const requestBody = await request.json(); diff --git a/packages/wrangler/src/agent-memory/client.ts b/packages/wrangler/src/agent-memory/client.ts index 73eb8d86fb..30c715da5c 100644 --- a/packages/wrangler/src/agent-memory/client.ts +++ b/packages/wrangler/src/agent-memory/client.ts @@ -30,7 +30,7 @@ export async function createNamespaceRequest( ): Promise { return await fetchResult( complianceConfig, - `/accounts/${accountId}/agentmemory/namespaces`, + `/accounts/${accountId}/agent-memory/namespaces`, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -45,7 +45,7 @@ export async function listNamespacesRequest( ): Promise { return await fetchListResult( complianceConfig, - `/accounts/${accountId}/agentmemory/namespaces` + `/accounts/${accountId}/agent-memory/namespaces` ); } @@ -56,7 +56,7 @@ export async function getNamespaceRequest( ): Promise { return await fetchResult( complianceConfig, - `/accounts/${accountId}/agentmemory/namespaces/${namespaceName}` + `/accounts/${accountId}/agent-memory/namespaces/${namespaceName}` ); } @@ -67,7 +67,7 @@ export async function deleteNamespaceRequest( ): Promise { await fetchResult( complianceConfig, - `/accounts/${accountId}/agentmemory/namespaces/${namespaceName}`, + `/accounts/${accountId}/agent-memory/namespaces/${namespaceName}`, { method: "DELETE" } ); } diff --git a/tools/e2e/common.ts b/tools/e2e/common.ts index 85b5eb7a58..efa018a34a 100644 --- a/tools/e2e/common.ts +++ b/tools/e2e/common.ts @@ -352,7 +352,7 @@ export const listTmpAgentMemoryNamespaces = async () => { const queryString = cursor ? "?" + new URLSearchParams({ cursor }).toString() : ""; - const url = `${baseUrl}/agentmemory/namespaces${queryString}`; + const url = `${baseUrl}/agent-memory/namespaces${queryString}`; const response = await fetch(url, { method: "GET", @@ -393,7 +393,7 @@ export const listTmpAgentMemoryNamespaces = async () => { }; export const deleteAgentMemoryNamespace = async (name: string) => { - return await apiFetch(`/agentmemory/namespaces/${name}`, "DELETE"); + return await apiFetch(`/agent-memory/namespaces/${name}`, "DELETE"); }; // Note: the container images functions below don't directly use the REST API since From 1de595f19352dbfcd44f75008e56c5914689da6e Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 5 May 2026 17:21:44 +0100 Subject: [PATCH 19/29] keep e2e namespace names below 32 chars --- packages/wrangler/e2e/helpers/generate-resource-name.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wrangler/e2e/helpers/generate-resource-name.ts b/packages/wrangler/e2e/helpers/generate-resource-name.ts index c26c5b4b06..e2cdb65622 100644 --- a/packages/wrangler/e2e/helpers/generate-resource-name.ts +++ b/packages/wrangler/e2e/helpers/generate-resource-name.ts @@ -1,5 +1,5 @@ import crypto from "node:crypto"; export function generateResourceName(type = "worker") { - return `tmp-e2e-${type}-${crypto.randomUUID()}`; + return `tmp-e2e-${type}-${crypto.randomUUID().slice(0, 8)}`; } From 24c2b066a1d170f684827340bf210e24bcdf84f0 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 5 May 2026 17:32:20 +0100 Subject: [PATCH 20/29] fixup creation test checks --- packages/wrangler/e2e/agent-memory.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/wrangler/e2e/agent-memory.test.ts b/packages/wrangler/e2e/agent-memory.test.ts index b8c72d7824..1fa5edf4a9 100644 --- a/packages/wrangler/e2e/agent-memory.test.ts +++ b/packages/wrangler/e2e/agent-memory.test.ts @@ -19,9 +19,8 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)("agent-memory", () => { ); expect(normalize(output.stdout)).toContain( - "✅ Created Agent Memory namespace" + `✅ Created Agent Memory namespace: "tmp-e2e-agent-memory"` ); - expect(normalize(output.stdout)).toContain(`Name: tmp-e2e-agent-memory`); expect(output.stderr).toBe(""); // Extract the namespace ID for use in subsequent tests From 5bd495834b1921c6173ea3a508d66b99e1216eac Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 5 May 2026 17:40:12 +0100 Subject: [PATCH 21/29] tidy up telemetry message --- packages/wrangler/src/agent-memory/delete.ts | 2 +- packages/wrangler/src/agent-memory/get.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/wrangler/src/agent-memory/delete.ts b/packages/wrangler/src/agent-memory/delete.ts index 0038ab37e8..c3c7867fcf 100644 --- a/packages/wrangler/src/agent-memory/delete.ts +++ b/packages/wrangler/src/agent-memory/delete.ts @@ -41,7 +41,7 @@ export const agentMemoryNamespaceDeleteCommand = createCommand({ if (e instanceof APIError && e.status === 404) { throw new UserError( `Agent Memory namespace "${namespace_name}" not found. Use 'wrangler agent-memory namespace list' to see available namespaces.`, - { telemetryMessage: "Agent Memory namespace not found" } + { telemetryMessage: "agent-memory namespace not found" } ); } throw e; diff --git a/packages/wrangler/src/agent-memory/get.ts b/packages/wrangler/src/agent-memory/get.ts index e108d4bfba..c366792bf6 100644 --- a/packages/wrangler/src/agent-memory/get.ts +++ b/packages/wrangler/src/agent-memory/get.ts @@ -33,7 +33,7 @@ export const agentMemoryNamespaceGetCommand = createCommand({ if (e instanceof APIError && e.status === 404) { throw new UserError( `Agent Memory namespace "${namespace_name}" not found. Use 'wrangler agent-memory namespace list' to see available namespaces.`, - { telemetryMessage: "Agent Memory namespace not found" } + { telemetryMessage: "agent-memory namespace not found" } ); } throw e; From a5454818ad59a411b7c57da21b709ebbe266c171 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 5 May 2026 17:50:09 +0100 Subject: [PATCH 22/29] fixup! keep e2e namespace names below 32 chars --- packages/wrangler/e2e/agent-memory.test.ts | 2 +- packages/wrangler/e2e/helpers/generate-resource-name.ts | 4 ++-- .../e2e/remote-binding/miniflare-remote-resources.test.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/wrangler/e2e/agent-memory.test.ts b/packages/wrangler/e2e/agent-memory.test.ts index 1fa5edf4a9..95c9c1b8c5 100644 --- a/packages/wrangler/e2e/agent-memory.test.ts +++ b/packages/wrangler/e2e/agent-memory.test.ts @@ -5,7 +5,7 @@ import { generateResourceName } from "./helpers/generate-resource-name"; import { normalizeOutput } from "./helpers/normalize"; describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)("agent-memory", () => { - const namespaceName = generateResourceName("agent-memory"); + const namespaceName = generateResourceName("agent-memory", 8); let namespaceId = ""; const helper = new WranglerE2ETestHelper(); diff --git a/packages/wrangler/e2e/helpers/generate-resource-name.ts b/packages/wrangler/e2e/helpers/generate-resource-name.ts index e2cdb65622..5b20b50dea 100644 --- a/packages/wrangler/e2e/helpers/generate-resource-name.ts +++ b/packages/wrangler/e2e/helpers/generate-resource-name.ts @@ -1,5 +1,5 @@ import crypto from "node:crypto"; -export function generateResourceName(type = "worker") { - return `tmp-e2e-${type}-${crypto.randomUUID().slice(0, 8)}`; +export function generateResourceName(type = "worker", maxLength?: number) { + return `tmp-e2e-${type}-${crypto.randomUUID().slice(0, maxLength)}`; } diff --git a/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts b/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts index 15205f107e..84f06ec857 100644 --- a/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts +++ b/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts @@ -486,7 +486,7 @@ const testCases: TestCase[] = [ name: "Agent Memory", scriptPath: "agent-memory.js", setup: async (helper) => { - const namespace = generateResourceName("agent-memory"); + const namespace = generateResourceName("agent-memory", 8); await helper.run(`wrangler agent-memory namespace create ${namespace}`); return { From e9de19de231e63e6c849864de1aef7201d77ea04 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 5 May 2026 22:17:08 +0100 Subject: [PATCH 23/29] fix e2e test colon mismatch in agent-memory create output --- packages/wrangler/e2e/agent-memory.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wrangler/e2e/agent-memory.test.ts b/packages/wrangler/e2e/agent-memory.test.ts index 95c9c1b8c5..fb62d84be3 100644 --- a/packages/wrangler/e2e/agent-memory.test.ts +++ b/packages/wrangler/e2e/agent-memory.test.ts @@ -19,7 +19,7 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)("agent-memory", () => { ); expect(normalize(output.stdout)).toContain( - `✅ Created Agent Memory namespace: "tmp-e2e-agent-memory"` + `✅ Created Agent Memory namespace "tmp-e2e-agent-memory"` ); expect(output.stderr).toBe(""); From 4b9703c8e09a8de0a888aacecc956c6750e894af Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 5 May 2026 22:46:24 +0100 Subject: [PATCH 24/29] expect open-beta warning in agent-memory e2e test stderr --- packages/wrangler/e2e/agent-memory.test.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/wrangler/e2e/agent-memory.test.ts b/packages/wrangler/e2e/agent-memory.test.ts index fb62d84be3..f112a43810 100644 --- a/packages/wrangler/e2e/agent-memory.test.ts +++ b/packages/wrangler/e2e/agent-memory.test.ts @@ -21,7 +21,9 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)("agent-memory", () => { expect(normalize(output.stdout)).toContain( `✅ Created Agent Memory namespace "tmp-e2e-agent-memory"` ); - expect(output.stderr).toBe(""); + expect(normalize(output.stderr)).toMatchInlineSnapshot(` + "▲ [WARNING] 🚧 \`wrangler agent-memory namespace create\` is an open beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose" + `); // Extract the namespace ID for use in subsequent tests const match = output.stdout.match(/ID:\s+(\S+)/); @@ -35,7 +37,9 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)("agent-memory", () => { const output = await helper.run(`wrangler agent-memory namespace list`); expect(normalize(output.stdout)).toContain("tmp-e2e-agent-memory"); - expect(output.stderr).toBe(""); + expect(normalize(output.stderr)).toMatchInlineSnapshot(` + "▲ [WARNING] 🚧 \`wrangler agent-memory namespace list\` is an open beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose" + `); }); it("list namespaces --json", async ({ expect }) => { @@ -50,7 +54,9 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)("agent-memory", () => { const found = parsed.find((ns) => ns.name === namespaceName); expect(found).toBeDefined(); expect(found?.id).toBe(namespaceId); - expect(output.stderr).toBe(""); + expect(normalize(output.stderr)).toMatchInlineSnapshot(` + "▲ [WARNING] 🚧 \`wrangler agent-memory namespace list\` is an open beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose" + `); }); it("get namespace", async ({ expect }) => { @@ -60,7 +66,9 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)("agent-memory", () => { expect(normalize(output.stdout)).toContain("tmp-e2e-agent-memory"); expect(output.stdout).toContain(namespaceId); - expect(output.stderr).toBe(""); + expect(normalize(output.stderr)).toMatchInlineSnapshot(` + "▲ [WARNING] 🚧 \`wrangler agent-memory namespace get\` is an open beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose" + `); }); it("delete namespace", async ({ expect }) => { @@ -71,6 +79,8 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)("agent-memory", () => { expect(normalize(output.stdout)).toContain( `✅ Deleted Agent Memory namespace tmp-e2e-agent-memory` ); - expect(output.stderr).toBe(""); + expect(normalize(output.stderr)).toMatchInlineSnapshot(` + "▲ [WARNING] 🚧 \`wrangler agent-memory namespace delete\` is an open beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose" + `); }); }); From 5b7b55475faff539963c9b27da6eb7a9ef72e025 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 6 May 2026 10:32:20 +0100 Subject: [PATCH 25/29] fix up e2e test and cleanup logic --- packages/wrangler/e2e/agent-memory.test.ts | 17 +++++++++-------- tools/e2e/common.ts | 4 +++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/wrangler/e2e/agent-memory.test.ts b/packages/wrangler/e2e/agent-memory.test.ts index f112a43810..8d9c40a772 100644 --- a/packages/wrangler/e2e/agent-memory.test.ts +++ b/packages/wrangler/e2e/agent-memory.test.ts @@ -15,22 +15,23 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)("agent-memory", () => { it("create namespace", async ({ expect }) => { const output = await helper.run( - `wrangler agent-memory namespace create ${namespaceName}` + `wrangler agent-memory namespace create ${namespaceName} --json` ); - expect(normalize(output.stdout)).toContain( - `✅ Created Agent Memory namespace "tmp-e2e-agent-memory"` - ); expect(normalize(output.stderr)).toMatchInlineSnapshot(` "▲ [WARNING] 🚧 \`wrangler agent-memory namespace create\` is an open beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose" `); // Extract the namespace ID for use in subsequent tests - const match = output.stdout.match(/ID:\s+(\S+)/); - if (!match) { - throw new Error("Could not extract namespace ID from create output"); + try { + const result = JSON.parse(output.stdout) as { name: string; id: string }; + expect(result.name).toEqual(namespaceName); + namespaceId = result.id; + } catch (cause) { + throw new Error("Could not extract namespace ID from create output", { + cause, + }); } - namespaceId = match[1]; }); it("list namespaces", async ({ expect }) => { diff --git a/tools/e2e/common.ts b/tools/e2e/common.ts index efa018a34a..464de58dc0 100644 --- a/tools/e2e/common.ts +++ b/tools/e2e/common.ts @@ -393,7 +393,9 @@ export const listTmpAgentMemoryNamespaces = async () => { }; export const deleteAgentMemoryNamespace = async (name: string) => { - return await apiFetch(`/agent-memory/namespaces/${name}`, "DELETE"); + const result = await apiFetch(`/agent-memory/namespaces/${name}`, "DELETE"); + // If successful the result is `null`, if the namespace is not found the result is `false` + return result !== false; }; // Note: the container images functions below don't directly use the REST API since From 2bf5b7110431e7716028477b7fc9f4059f4a5df8 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 6 May 2026 16:36:28 +0100 Subject: [PATCH 26/29] test: retry transient API 5xx in remote-binding e2e suite Wrap startRemoteProxySession() in retry() to handle transient Cloudflare API 5xx (most commonly edge-preview returning 500), which has been blocking shard 3 of this PR consistently for 2+ days. Test-only change; no runtime behaviour modified. Refs: #13831 --- .../miniflare-remote-resources.test.ts | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts b/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts index 84f06ec857..4d8e0797ba 100644 --- a/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts +++ b/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts @@ -8,6 +8,7 @@ import { WranglerE2ETestHelper, } from "../helpers/e2e-wrangler-test"; import { generateResourceName } from "../helpers/generate-resource-name"; +import { retry } from "../helpers/retry"; import type { StartDevWorkerInput } from "../../src/api"; import type { StartRemoteProxySessionOptions } from "../../src/cli"; import type { RawConfig } from "@cloudflare/workers-utils"; @@ -680,19 +681,27 @@ if (!CLOUDFLARE_ACCOUNT_ID) { for (const testCase of activeTestCases) { testConfigs.push(await testCase.setup(helper)); } - const remoteProxySession = await startRemoteProxySession( - Object.assign( - {}, - ...testConfigs.map( - (config) => config.remoteProxySessionConfig.bindings - ) - ), - Object.assign( - {}, - ...testConfigs.map( - (config) => config.remoteProxySessionConfig.options - ) - ) + // Retry transient Cloudflare API 5xx (e.g. edge-preview 500) during + // proxy session setup. See cloudflare/workers-sdk#13831 for the + // broader fix. + const remoteProxySession = await retry( + () => false, + () => + startRemoteProxySession( + Object.assign( + {}, + ...testConfigs.map( + (config) => config.remoteProxySessionConfig.bindings + ) + ), + Object.assign( + {}, + ...testConfigs.map( + (config) => config.remoteProxySessionConfig.options + ) + ) + ), + 5 ); const testCaseModules = activeTestCases.map((testCase) => ({ @@ -803,9 +812,17 @@ if (!CLOUDFLARE_ACCOUNT_ID) { const helper = new WranglerE2ETestHelper(); await helper.seed(path.resolve(__dirname, "./workers")); const testConfig = await mtlsTestCase.setup(helper); - const remoteProxySession = await startRemoteProxySession( - testConfig.remoteProxySessionConfig.bindings, - testConfig.remoteProxySessionConfig.options + // Retry transient Cloudflare API 5xx (e.g. edge-preview 500) during + // proxy session setup. See cloudflare/workers-sdk#13831 for the + // broader fix. + const remoteProxySession = await retry( + () => false, + () => + startRemoteProxySession( + testConfig.remoteProxySessionConfig.bindings, + testConfig.remoteProxySessionConfig.options + ), + 5 ); const miniflareConfig: MiniflareOptions = Object.assign( { From 53879be711ab48a61470d7a9777d1ccd70ef5fd1 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Thu, 7 May 2026 14:03:30 +0100 Subject: [PATCH 27/29] test: refactor remote-binding e2e to use updateBindings per test Boot the remote proxy session in beforeAll with only the first test case's bindings, then call updateBindings inside each `it` to install that test case's bindings. Previously every test case's bindings were merged into a single startRemoteProxySession call, causing the boot to time out as the suite grew. The new flow makes the boot smaller and attributes any per-binding boot failure to the test that owns that binding rather than to an opaque beforeAll timeout. Also drop the retry wrapper around startRemoteProxySession in both the main and mTLS describe blocks. --- .../miniflare-remote-resources.test.ts | 235 ++++++++---------- 1 file changed, 102 insertions(+), 133 deletions(-) diff --git a/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts b/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts index 4d8e0797ba..2a9f21f3a0 100644 --- a/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts +++ b/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts @@ -8,9 +8,7 @@ import { WranglerE2ETestHelper, } from "../helpers/e2e-wrangler-test"; import { generateResourceName } from "../helpers/generate-resource-name"; -import { retry } from "../helpers/retry"; -import type { StartDevWorkerInput } from "../../src/api"; -import type { StartRemoteProxySessionOptions } from "../../src/cli"; +import type { RemoteProxySession, StartDevWorkerInput } from "../../src/api"; import type { RawConfig } from "@cloudflare/workers-utils"; import type { Awaitable, @@ -63,11 +61,11 @@ interface TestCase { */ interface TestConfig { /** - * These bindings and options objects will be merged with all the other test cases to create a single remote proxy session for all tests. + * The bindings to install on the shared remote proxy session via + * `updateBindings` when this test case runs. */ remoteProxySessionConfig: { bindings: StartDevWorkerInput["bindings"]; - options?: StartRemoteProxySessionOptions; }; /** * The Miniflare config (mostly bindings) for this test case. This will be merged with all other test cases to create a single Miniflare instance for all tests. @@ -671,37 +669,25 @@ if (!CLOUDFLARE_ACCOUNT_ID) { describe("Remote bindings (remote proxy session enabled)", () => { let helper: WranglerE2ETestHelper; let mf: MiniflareType; + let remoteProxySession: RemoteProxySession; + const testConfigByTestCase = new Map(); const onTeardown = useTeardown({ timeout: testCases.length * 15_000 }); const activeTestCases = testCases.filter((testCase) => !testCase.skip); beforeAll(async () => { helper = new WranglerE2ETestHelper(onTeardown); await helper.seed(path.resolve(__dirname, "./workers")); - const testConfigs: TestConfig[] = []; for (const testCase of activeTestCases) { - testConfigs.push(await testCase.setup(helper)); + testConfigByTestCase.set(testCase, await testCase.setup(helper)); } - // Retry transient Cloudflare API 5xx (e.g. edge-preview 500) during - // proxy session setup. See cloudflare/workers-sdk#13831 for the - // broader fix. - const remoteProxySession = await retry( - () => false, - () => - startRemoteProxySession( - Object.assign( - {}, - ...testConfigs.map( - (config) => config.remoteProxySessionConfig.bindings - ) - ), - Object.assign( - {}, - ...testConfigs.map( - (config) => config.remoteProxySessionConfig.options - ) - ) - ), - 5 + const testConfigs = [...testConfigByTestCase.values()]; + + // Boot the proxy session with only the first test case's bindings. + // Each `it` will then call `updateBindings` to swap in its own bindings. + // This makes the boot smaller (and faster) and pinpoints any per-binding + // issue to the test that owns that binding rather than the beforeAll hook. + remoteProxySession = await startRemoteProxySession( + testConfigs[0].remoteProxySessionConfig.bindings ); const testCaseModules = activeTestCases.map((testCase) => ({ @@ -709,6 +695,10 @@ if (!CLOUDFLARE_ACCOUNT_ID) { path: path.resolve(helper.tmpPath, testCase.scriptPath), })); + // The proxy connection string is stable across `updateBindings` calls, + // so we can build the Miniflare instance once with all bindings merged. + // Each test script only touches its own binding (selected via the + // `x-test-module` header), so unused entries are dormant. const miniflareConfig: MiniflareOptions = Object.assign( { compatibilityDate: "2025-09-06", @@ -729,122 +719,101 @@ if (!CLOUDFLARE_ACCOUNT_ID) { }, activeTestCases.length * 15_000); for (const testCase of activeTestCases) { - it("should work for " + testCase.name, async ({ expect }) => { - const resp = await mf.dispatchFetch("http://example.com/", { - headers: { "x-test-module": testCase.scriptPath }, - }); - const respText = await resp.text(); - testCase.getExpectFetchToMatch(expect).forEach((match) => { - expect(respText).toEqual(match); - }); - }); + it( + "should work for " + testCase.name, + async ({ expect }) => { + const testConfig = testConfigByTestCase.get(testCase); + assert(testConfig, `Missing test config for ${testCase.name}`); + await remoteProxySession.updateBindings( + testConfig.remoteProxySessionConfig.bindings + ); + const resp = await mf.dispatchFetch("http://example.com/", { + headers: { "x-test-module": testCase.scriptPath }, + }); + const respText = await resp.text(); + testCase.getExpectFetchToMatch(expect).forEach((match) => { + expect(respText).toEqual(match); + }); + }, + 45_000 + ); } }); - // Separate describe block for mTLS since it needs a custom remote-binding proxy worker deployment + // Separate describe block for mTLS because it needs a custom remote-binding + // proxy worker pre-deployed with `mtls_certificates` configured. That can't + // be done via runtime `bindings:` alone, so it can't share the standard + // proxy worker the other test cases use. describe("Remote bindings (mtls)", () => { - const mtlsTestCase: TestCase = { - name: "mTLS", - scriptPath: "mtls.js", - setup: async (helper) => { - const certificateId = await helper.cert(); - // We need to override the standard Wrangler remote proxy worker with one that has the mTLS configured. - const workerName = generateResourceName(); - const wranglerConfig: RawConfig = { - name: workerName, - main: "worker.js", - mtls_certificates: [ - { - certificate_id: certificateId, - binding: "MTLS", - }, - ], - }; - await helper.seed({ - "worker.js": dedent /* javascript */ ` - export default { - fetch(request) { return new Response("Hello"); } - } - `, - "pre-deployment-wrangler.json": JSON.stringify( - wranglerConfig, - null, - 2 - ), - }); - // Deploy the custom remote proxy worker for this tests - await helper.worker({ - workerName, - configPath: "pre-deployment-wrangler.json", - }); - return { - remoteProxySessionConfig: { - bindings: { - MTLS: { - type: "mtls_certificate", - certificate_id: certificateId, - }, - }, - // This is the big difference that means we cannot use the standard remote proxy worker - // This worker needs to have mTLS certificates configured. - options: { - workerName, - }, - }, - miniflareConfig: (connection) => ({ - mtlsCertificates: { - MTLS: { - certificate_id: certificateId, - remoteProxyConnectionString: connection, - }, - }, - }), - }; - }, - getExpectFetchToMatch: (expect) => [ - // Note: in this test we are making sure that TLS negotiation does work by checking that we get an SSL certificate error - expect.stringMatching(/The SSL certificate error/), - expect.not.stringMatching(/No required SSL certificate was sent/), - ], - }; - it("should work for mTLS bindings", async ({ expect }) => { const helper = new WranglerE2ETestHelper(); await helper.seed(path.resolve(__dirname, "./workers")); - const testConfig = await mtlsTestCase.setup(helper); - // Retry transient Cloudflare API 5xx (e.g. edge-preview 500) during - // proxy session setup. See cloudflare/workers-sdk#13831 for the - // broader fix. - const remoteProxySession = await retry( - () => false, - () => - startRemoteProxySession( - testConfig.remoteProxySessionConfig.bindings, - testConfig.remoteProxySessionConfig.options - ), - 5 - ); - const miniflareConfig: MiniflareOptions = Object.assign( + + const certificateId = await helper.cert(); + // Override the standard Wrangler remote proxy worker with one that has + // the mTLS certificate configured at the wrangler-config level. + const workerName = generateResourceName(); + const wranglerConfig: RawConfig = { + name: workerName, + main: "worker.js", + mtls_certificates: [ + { + certificate_id: certificateId, + binding: "MTLS", + }, + ], + }; + await helper.seed({ + "worker.js": dedent /* javascript */ ` + export default { + fetch(request) { return new Response("Hello"); } + } + `, + "pre-deployment-wrangler.json": JSON.stringify(wranglerConfig, null, 2), + }); + // Deploy the custom remote proxy worker for this test + await helper.worker({ + workerName, + configPath: "pre-deployment-wrangler.json", + }); + + const remoteProxySession = await startRemoteProxySession( { - compatibilityDate: "2025-09-06", - modules: [ - { - type: "ESModule", - path: path.resolve(helper.tmpPath, mtlsTestCase.scriptPath), - }, - ], - modulesRoot: helper.tmpPath, - } satisfies MiniflareOptions, - testConfig.miniflareConfig( - remoteProxySession.remoteProxyConnectionString - ) + MTLS: { + type: "mtls_certificate", + certificate_id: certificateId, + }, + }, + { workerName } ); - const mf = new Miniflare(miniflareConfig); + + const mf = new Miniflare({ + compatibilityDate: "2025-09-06", + modules: [ + { + type: "ESModule", + path: path.resolve(helper.tmpPath, "mtls.js"), + }, + ], + modulesRoot: helper.tmpPath, + mtlsCertificates: { + MTLS: { + certificate_id: certificateId, + remoteProxyConnectionString: + remoteProxySession.remoteProxyConnectionString, + }, + }, + }); const resp = await mf.dispatchFetch("http://example.com/"); const respText = await resp.text(); - mtlsTestCase.getExpectFetchToMatch(expect).forEach((match) => { - expect(respText).toEqual(match); - }); + // Check that TLS negotiation does work by checking that we get an SSL + // certificate error rather than a "no certificate sent" error. + expect(respText).toEqual( + expect.stringMatching(/The SSL certificate error/) + ); + expect(respText).toEqual( + expect.not.stringMatching(/No required SSL certificate was sent/) + ); }); }); } From 50cc148e108a29cd953d1cf00dae51460a870287 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Thu, 7 May 2026 15:24:27 +0100 Subject: [PATCH 28/29] test: fix agent-memory e2e to match runtime binding API The agent-memory worker binding API uses `getProfile()` (not `getContext()`), and the result's text field is `profile` (not `context`). Update the test worker and assertion to match. --- .../e2e/remote-binding/miniflare-remote-resources.test.ts | 2 +- packages/wrangler/e2e/remote-binding/workers/agent-memory.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts b/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts index 2a9f21f3a0..7b5c8bf151 100644 --- a/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts +++ b/packages/wrangler/e2e/remote-binding/miniflare-remote-resources.test.ts @@ -507,7 +507,7 @@ const testCases: TestCase[] = [ }), }; }, - getExpectFetchToMatch: (expect) => [expect.stringContaining(`"context"`)], + getExpectFetchToMatch: (expect) => [expect.stringContaining("profile")], }, { name: "Pipelines", diff --git a/packages/wrangler/e2e/remote-binding/workers/agent-memory.js b/packages/wrangler/e2e/remote-binding/workers/agent-memory.js index 8f47bd1c80..30ad8158b2 100644 --- a/packages/wrangler/e2e/remote-binding/workers/agent-memory.js +++ b/packages/wrangler/e2e/remote-binding/workers/agent-memory.js @@ -1,7 +1,7 @@ export default { async fetch(_request, env) { - const memoryContext = env.MEMORY.getContext("wrangler-e2e"); - const summary = await memoryContext.getSummary(); + const profile = env.MEMORY.getProfile("wrangler-e2e"); + const summary = await profile.getSummary(); return new Response(JSON.stringify(summary)); }, }; From 91d2b2814f3e10b1c4db807297898feacc0aa21d Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 20 May 2026 14:42:44 +0100 Subject: [PATCH 29/29] [wrangler] Fix agent-memory tests after upstream changes - Update unit test to import runInTempDir from @cloudflare/workers-utils/test-helpers (the helper was moved out of packages/wrangler/src/__tests__/helpers/ in main). - Drop the open-beta status warning expectation from the two e2e tests that run with --json. PR #13837 made the status warning honor the printBanner result, so commands with printBanner: (args) => !args.json now suppress the warning in JSON mode. --- packages/wrangler/e2e/agent-memory.test.ts | 12 ++++++------ packages/wrangler/src/__tests__/agent-memory.test.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/wrangler/e2e/agent-memory.test.ts b/packages/wrangler/e2e/agent-memory.test.ts index 8d9c40a772..6f125e1e64 100644 --- a/packages/wrangler/e2e/agent-memory.test.ts +++ b/packages/wrangler/e2e/agent-memory.test.ts @@ -18,9 +18,9 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)("agent-memory", () => { `wrangler agent-memory namespace create ${namespaceName} --json` ); - expect(normalize(output.stderr)).toMatchInlineSnapshot(` - "▲ [WARNING] 🚧 \`wrangler agent-memory namespace create\` is an open beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose" - `); + // The open-beta status warning is suppressed when --json is used + // (printBanner returns false), so stderr should be empty. + expect(output.stderr).toBe(""); // Extract the namespace ID for use in subsequent tests try { @@ -55,9 +55,9 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)("agent-memory", () => { const found = parsed.find((ns) => ns.name === namespaceName); expect(found).toBeDefined(); expect(found?.id).toBe(namespaceId); - expect(normalize(output.stderr)).toMatchInlineSnapshot(` - "▲ [WARNING] 🚧 \`wrangler agent-memory namespace list\` is an open beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose" - `); + // The open-beta status warning is suppressed when --json is used + // (printBanner returns false), so stderr should be empty. + expect(output.stderr).toBe(""); }); it("get namespace", async ({ expect }) => { diff --git a/packages/wrangler/src/__tests__/agent-memory.test.ts b/packages/wrangler/src/__tests__/agent-memory.test.ts index ab55513ffc..e9aa61ec97 100644 --- a/packages/wrangler/src/__tests__/agent-memory.test.ts +++ b/packages/wrangler/src/__tests__/agent-memory.test.ts @@ -1,4 +1,5 @@ import { UserError } from "@cloudflare/workers-utils"; +import { runInTempDir } from "@cloudflare/workers-utils/test-helpers"; import { http, HttpResponse } from "msw"; import { afterEach, describe, it } from "vitest"; import { endEventLoop } from "./helpers/end-event-loop"; @@ -7,7 +8,6 @@ import { mockConsoleMethods } from "./helpers/mock-console"; import { clearDialogs, mockConfirm } from "./helpers/mock-dialogs"; import { useMockIsTTY } from "./helpers/mock-istty"; import { createFetchResult, msw } from "./helpers/msw"; -import { runInTempDir } from "./helpers/run-in-tmp"; import { runWrangler } from "./helpers/run-wrangler"; import type { AgentMemoryNamespace } from "../agent-memory/client";