Validierte Support-Tools hinzufügen

CloudflareBeginner
Jetzt üben

Einführung

Ein Sprachmodell kann vorschlagen, was zu tun ist. Ein Tool ermöglicht ihm jedoch, gezielt eine serverseitige Operation anzufordern. Diese Grenze erfordert mehr Sorgfalt als ein gewöhnlicher Chat: Vom Modell erzeugte Argumente sind nicht vertrauenswürdig, und eine formal korrekte Anfrage kann trotzdem auf die falsche Support-Warteschlange zielen oder neuere Arbeit überschreiben.

In diesem Lab statten Sie einen AIChatAgent mit zwei bewusst kleinen Tools aus:

  1. lookupSupportCase liest einen synthetischen Fall aus dem eigenen SQLite-Speicher des benannten Agents.
  2. setSupportPriority ändert ausschließlich diesen synthetischen Datensatz.
  3. Zod-Schemas weisen fehlerhafte Argumente zurück, bevor eine der beiden Operationen ausgeführt wird.
  4. Serverseitige Prüfungen erzwingen den Agent-Namen, die Ticket-ID und die erwartete Revision.
  5. Eine begrenzte Workers-AI-Runde darf die Tools aufrufen, während ein unabhängiger Test dieselben Operationen deterministisch überprüft.

Der beschreibbare Datensatz ist synthetisch und kann gefahrlos gelöscht werden; ein echtes Helpdesk-System ist nicht verbunden. Das ist wichtig, weil die Schema-Validierung die Frage „Ist die Eingabe korrekt aufgebaut?“ beantwortet, während Autorisierungs- und Bereichsprüfungen klären: „Darf dieser Agent diesen Datensatz ändern?“ Im nächsten Lab fügen Sie vor einer Wirkung eine separate Freigabe durch einen Menschen hinzu.

Die bereitgestellte React-Seite und das kurzlebige Sitzungstoken halten den Fokus auf dem Tool-Design und nicht auf Frontend- oder Authentifizierungs-Standardcode. Die kostenlosen Workers-AI-Kontingente werden mit anderen Aktivitäten des Kontos geteilt. Wenn für das Konto kein Kontingent mehr verfügbar ist, beenden Sie das Lab, statt einen kostenpflichtigen Tarif zu aktivieren.

Bevor Sie diesen Kurs direkt beginnen, absolvieren Sie LabEx mit Ihrem Cloudflare-Konto verbinden. Jede neue LabEx-VM benötigt eine eigene Wrangler-Autorisierung. Frühere Labs des Kurses werden empfohlen, ihre VMs und Ressourcen werden hier jedoch niemals wiederverwendet.

Die VM autorisieren und den Tool-Worker deklarieren

In diesem Schritt autorisieren Sie die neue VM und deklarieren die Ressourcen, die der Tool-fähige Agent verwendet.

Öffnen Sie ein Terminal und wechseln Sie in das vorbereitete Projekt:

cd /home/labex/project/validated-support-tools

Autorisieren Sie diese VM:

npx wrangler login

Öffnen Sie den angezeigten Link, bestätigen Sie die dokumentierten Wrangler-Berechtigungen für Ihr persönliches Lernkonto und kehren Sie anschließend zum Terminal zurück. Prüfen Sie das strukturierte Ergebnis:

npx wrangler whoami --json

Suchen Sie nach "loggedIn": true, bestätigen Sie den Kontonamen und kopieren Sie die tatsächliche ID dieses Kontos. Speichern Sie sie zusammen mit einem eindeutigen, kurzlebigen Worker-Namen:

ACCOUNT_ID="paste-your-confirmed-account-id"
RUN="labex-c11-s05-$(openssl rand -hex 6)"
cat > wrangler.jsonc <<JSON
{
  "\$schema": "./node_modules/wrangler/config-schema.json",
  "name": "$RUN",
  "account_id": "$ACCOUNT_ID",
  "main": "src/server.ts",
  "compatibility_date": "2026-09-18",
  "compatibility_flags": ["nodejs_compat"],
  "workers_dev": true,
  "preview_urls": false,
  "observability": { "enabled": true },
  "ai": { "binding": "AI", "remote": true },
  "durable_objects": {
    "bindings": [
      { "name": "SupportToolsAgent", "class_name": "SupportToolsAgent" }
    ]
  },
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["SupportToolsAgent"] }
  ]
}
JSON
python3 .labex/verify.py authorization

Das AI-Binding stellt die Modellinferenz bereit, ohne dass Sie einen API-Schlüssel einbetten müssen. Das Durable-Object-Binding gibt jedem benannten SupportToolsAgent einen eigenen SQLite-Speicher. Der Browser verwendet den Namen planning; ein anderer Name erhält eine separate Instanz und kann die Daten von planning nicht sehen. Bisher wurde noch nichts bereitgestellt.

Die Tool-Verträge definieren

In diesem Schritt legen Sie genau fest, welche Argumente die einzelnen Tools akzeptieren.

Ein Tool-Schema ist ein Vertrag, der zur Laufzeit geprüft wird. TypeScript-Typen helfen beim Kompilieren. Die Modellausgabe trifft jedoch zur Laufzeit ein und muss erneut geprüft werden. Erstellen Sie src/cases.ts:

cat > src/cases.ts <<'TS'
import { z } from "zod";

const queue = z.string()
  .min(3)
  .max(40)
  .regex(/^[a-z0-9-]+$/, "queue must use lowercase letters, digits or hyphens");

export const lookupCaseInput = z.object({
  queue,
  ticketId: z.literal("T-SYNTH-101")
}).strict();

export const updatePriorityInput = lookupCaseInput.extend({
  priority: z.enum(["low", "medium", "high"]),
  expectedRevision: z.number().int().nonnegative()
}).strict();

export type LookupCaseInput = z.infer<typeof lookupCaseInput>;
export type UpdatePriorityInput = z.infer<typeof updatePriorityInput>;
export type SupportCase = {
  queue: string;
  ticketId: "T-SYNTH-101";
  summary: string;
  priority: "low" | "medium" | "high";
  revision: number;
};

export function parseInput<T>(schema: z.ZodType<T>, input: unknown): T {
  const result = schema.safeParse(input);
  if (!result.success) {
    const issue = result.error.issues[0];
    throw new Error(`invalid tool input: ${issue.path.join(".") || "request"} ${issue.message}`);
  }
  return result.data;
}
TS
python3 .labex/verify.py schemas

Der Lesevertrag akzeptiert nur einen gültigen Namen der Warteschlange und das eine synthetische Ticket. Der Aktualisierungsvertrag ergänzt eine Priorität aus einer festen Aufzählung und eine nicht negative Ganzzahl für die Revision. .strict() weist außerdem unerwartete Felder zurück. Dadurch werden Mehrdeutigkeiten reduziert und nicht unterstützte Anweisungen können nicht in die Operation eingeschleust werden.

expectedRevision ist eine Prüfung auf optimistische Nebenläufigkeit. Der Aufrufer gibt an, welche Version er gesehen hat. Der Server weist die Aktualisierung zurück, wenn diese Version bereits geändert wurde. Die Validierung gewährt selbst keinen Zugriff – der Agent vergleicht queue zusätzlich mit seinem eigenen dauerhaften Namen.

Bereichsgebundene serverseitige Tools implementieren

In diesem Schritt verbinden Sie beide Schemas mit einem Datensatz, der lokal zu einem Agent gehört, und stellen dieselbe Implementierung dem Modell sowie dem deterministischen Prüfer bereit.

Erstellen Sie src/server.ts:

cat > src/server.ts <<'TS'
import { AIChatAgent, type OnChatMessageOptions } from "@cloudflare/ai-chat";
import { callable, routeAgentRequest } from "agents";
import { convertToModelMessages, stepCountIs, streamText, tool } from "ai";
import { createWorkersAI } from "workers-ai-provider";
import {
  lookupCaseInput,
  parseInput,
  type LookupCaseInput,
  type SupportCase,
  type UpdatePriorityInput,
  updatePriorityInput
} from "./cases";
import { verifySessionRequest } from "./session-auth";

export class SupportToolsAgent extends AIChatAgent<Cloudflare.Env> {
  maxPersistedMessages = 12;

  private ensureCase(): void {
    this.sql`CREATE TABLE IF NOT EXISTS support_cases (
      ticket_id TEXT PRIMARY KEY,
      queue TEXT NOT NULL,
      case_summary TEXT NOT NULL,
      priority TEXT NOT NULL,
      revision INTEGER NOT NULL
    )`;
    this.sql`INSERT OR IGNORE INTO support_cases
      (ticket_id, queue, case_summary, priority, revision)
      VALUES ('T-SYNTH-101', ${this.name}, 'Synthetic customer cannot open a sample invoice', 'medium', 0)`;
  }

  private scopedCase(input: LookupCaseInput): SupportCase {
    if (input.queue !== this.name) throw new Error("queue is outside this Agent scope");
    this.ensureCase();
    const rows = this.sql<{
      queue: string;
      ticketId: "T-SYNTH-101";
      summary: string;
      priority: "low" | "medium" | "high";
      revision: number;
    }>`SELECT queue, ticket_id AS ticketId, case_summary AS summary, priority, revision
       FROM support_cases WHERE ticket_id = ${input.ticketId}`;
    const record = rows[0];
    if (!record || record.queue !== this.name) throw new Error("case not found in this Agent scope");
    return record;
  }

  @callable()
  inspectCase(input: unknown): SupportCase {
    return this.scopedCase(parseInput(lookupCaseInput, input));
  }

  @callable()
  setPriority(input: unknown): SupportCase {
    const parsed: UpdatePriorityInput = parseInput(updatePriorityInput, input);
    const current = this.scopedCase(parsed);
    if (parsed.expectedRevision !== current.revision) {
      throw new Error(`revision conflict: current revision is ${current.revision}`);
    }
    this.sql`UPDATE support_cases
      SET priority = ${parsed.priority}, revision = ${current.revision + 1}
      WHERE ticket_id = ${parsed.ticketId} AND queue = ${this.name}`;
    const changed = this.scopedCase(parsed);
    console.log(JSON.stringify({
      event: "tool_event",
      tool: "setSupportPriority",
      instance: this.name,
      revision: changed.revision
    }));
    return changed;
  }

  async onChatMessage(_onFinish: unknown, options?: OnChatMessageOptions) {
    const tools = {
      lookupSupportCase: tool({
        description: "Read synthetic ticket T-SYNTH-101 only from the current named support queue.",
        inputSchema: lookupCaseInput,
        execute: async (input) => this.inspectCase(input)
      }),
      setSupportPriority: tool({
        description: "Set low, medium or high priority on synthetic ticket T-SYNTH-101 in the current queue, using its observed revision.",
        inputSchema: updatePriorityInput,
        execute: async (input) => this.setPriority(input)
      })
    };
    const workersai = createWorkersAI({ binding: this.env.AI });
    const result = streamText({
      model: workersai("@cf/zai-org/glm-4.7-flash", {
        reasoning_effort: null,
        chat_template_kwargs: { enable_thinking: false }
      }),
      system: `You assist only the synthetic ${this.name} queue. Use tools for case facts or changes. Never invent tool results, other queues or credentials. Keep the final answer to one short sentence.`,
      messages: await convertToModelMessages(this.messages),
      tools,
      stopWhen: stepCountIs(4),
      maxOutputTokens: 96,
      temperature: 0,
      abortSignal: options?.abortSignal
    });
    return result.toUIMessageStreamResponse();
  }
}

export default {
  async fetch(request: Request, env: Cloudflare.Env): Promise<Response> {
    const authorize = (candidate: Request, route: { name: string }) =>
      verifySessionRequest(candidate, route.name, env.SESSION_SIGNING_KEY);
    return (await routeAgentRequest(request, env, {
      onBeforeConnect: authorize,
      onBeforeRequest: authorize
    })) ?? new Response("Not found", { status: 404 });
  }
};
TS
python3 .labex/verify.py server

Das Modell erhält niemals direkten Datenbankzugriff. Es schlägt typisierte Argumente vor; execute ruft Code im Durable Object auf, wo der Server den aktuellen Agent-Namen erneut prüft. Die beiden Methoden mit @callable() verwenden dieselben Codepfade. Dadurch kann der Prüfer fehlerhafte Anfragen, Anfragen außerhalb des Bereichs und veraltete Anfragen testen, ohne von nicht deterministischen Modellentscheidungen abzuhängen.

Die Datenbank wird innerhalb jedes benannten Agents verzögert erstellt. INSERT OR IGNORE stellt ein begrenztes Testobjekt bereit, ohne eine vorherige Aktualisierung zu überschreiben. Protokolliert werden nur Metadaten – Tool-Name, Agent-Instanz und Revision –, nicht der Falltext.

Die Tool-fähige Chat-Seite verbinden

In diesem Schritt verbinden Sie die bereitgestellte Seitengrundstruktur und zeigen Tool-Aktivitäten getrennt vom Assistententext an.

Erstellen Sie die TypeScript- und Vite-Konfiguration:

cat > tsconfig.json <<'JSON'
{
  "extends": "agents/tsconfig",
  "compilerOptions": {
    "jsx": "react-jsx",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "types": ["@cloudflare/workers-types", "vite/client", "node"]
  },
  "include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts", "worker-configuration.d.ts"]
}
JSON

cat > vite.config.ts <<'TS'
import { cloudflare } from "@cloudflare/vite-plugin";
import react from "@vitejs/plugin-react";
import agents from "agents/vite";
import { defineConfig } from "vite";

export default defineConfig({ plugins: [react(), agents(), cloudflare()] });
TS

Erstellen Sie src/client.tsx:

cat > src/client.tsx <<'TSX'
import { useAgentChat } from "@cloudflare/ai-chat/react";
import { useAgent } from "agents/react";
import { Suspense } from "react";
import { createRoot } from "react-dom/client";

function ToolsChat() {
  const parameters = new URLSearchParams(window.location.search);
  const session = parameters.get("session") ?? "";
  const token = parameters.get("token") ?? "";
  if (!session || !token) {
    return <main><h1>Signed session required</h1><p className="help">Open the complete URL printed by the token command.</p></main>;
  }

  const agent = useAgent({
    agent: "SupportToolsAgent",
    name: session,
    host: window.location.host,
    query: { token }
  });
  const { messages, sendMessage, status, error } = useAgentChat({ agent });

  return (
    <main>
      <p className="eyebrow">Validated server-side tools</p>
      <h1>Synthetic Support Console</h1>
      <p className="scope">Allowed queue: <strong>{session}</strong> · allowed ticket: <strong>T-SYNTH-101</strong></p>
      <p className="status">Status: <strong>{status}</strong></p>
      <section className="messages" aria-live="polite">
        {messages.length === 0 && <p className="empty">No tool requests in this signed session yet.</p>}
        {messages.map((message) => (
          <article className={`message ${message.role}`} key={message.id}>
            <span className="role">{message.role}</span>
            {message.parts.map((part, index) => {
              if (part.type === "text") return <span key={index}>{part.text}</span>;
              if (part.type.startsWith("tool-")) {
                return <span className="tool" key={index}>{part.type.replace("tool-", "tool: ")}</span>;
              }
              return null;
            })}
          </article>
        ))}
      </section>
      <form => {
        event.preventDefault();
        const input = event.currentTarget.elements.namedItem("message") as HTMLInputElement;
        const text = input.value.trim();
        if (!text) return;
        sendMessage({ text });
        input.value = "";
      }}>
        <input name="message" defaultValue={`Look up T-SYNTH-101 in ${session}, then set its priority to high using the current revision. Briefly confirm the result.`} maxLength={220} aria-label="Tool request" />
        <button type="submit" disabled={status === "streaming" || status === "submitted"}>Send</button>
      </form>
      <p className="notice">Training fixture only: this page cannot reach a real support system.</p>
      {error && <p className="error" role="alert">{error.message}</p>}
    </main>
  );
}

createRoot(document.getElementById("root")!).render(
  <Suspense fallback={<main><p>Restoring the signed tool session…</p></main>}><ToolsChat /></Suspense>
);
TSX
python3 .labex/verify.py client

useAgent() verbindet sich mit genau einem benannten Agent und verwendet dafür das kurzlebige Token. useAgentChat() rendert die dauerhafte Konversation und die gestreamte Antwort. Tool-Teile werden als Aktivität gekennzeichnet und nicht in den Assistententext integriert. So kann die lernende Person unterscheiden, ob „das Modell eine Operation angefordert hat“ oder „das Modell Text geschrieben hat“. Der Browser kann die serverseitige Validierung weiterhin nicht umgehen.

Die Grenzen lokal bauen und prüfen

In diesem Schritt kompilieren Sie die Anwendung und testen die tatsächliche Tool-Implementierung, ohne einen Modellaufruf zu verbrauchen.

Erzeugen Sie exakte Umgebungstypen, führen Sie die Typprüfung aus und erstellen Sie beide Bundles:

npx wrangler types
npm run check
npm run build
python3 .labex/verify.py build

Wrangler leitet Cloudflare.Env aus den tatsächlichen Bindings ab. Dadurch kann eine manuell geschriebene Umgebungsschnittstelle nicht von wrangler.jsonc abweichen.

Workers AI ist ein Remote-Binding. Die lokale Laufzeit benötigt daher den bereits von Wrangler gespeicherten OAuth-Zugriff. Übergeben Sie ihn nur an den untergeordneten Prozess und löschen Sie anschließend sofort die Kopie in der Shell:

DEV_PROXY_TOKEN="$(npx wrangler auth token --json | node -e 'let data="";process.stdin.on("data",chunk=>data+=chunk).on("end",()=>process.stdout.write(JSON.parse(data).token))')"
CLOUDFLARE_API_TOKEN="$DEV_PROXY_TOKEN" CI=true npm run dev > .labex/dev.log 2>&1 < /dev/null &
echo $! > .labex/dev.pid
unset DEV_PROXY_TOKEN
for attempt in $(seq 1 40); do
  curl --silent --fail http://127.0.0.1:5173/ > /dev/null && break
  sleep 1
done
tail -n 12 .labex/dev.log
python3 .labex/verify.py local

Geben Sie den temporären OAuth-Wert nicht aus und speichern Sie ihn nicht in .dev.vars. Der unabhängige Test verwendet einen zufälligen benannten Agent und ruft dieselben Methoden inspectCase() und setPriority() auf, die auch von den Modell-Tools verwendet werden. Er weist Folgendes nach:

  • Die anfängliche Priorität ist medium, die Revision ist 0.
  • Fehlerhafte Lesevorgänge und Lesevorgänge über eine andere Warteschlange schlagen fehl.
  • Eine gültige Aktualisierung setzt die Priorität auf high und die Revision auf 1.
  • Die erneute Verwendung der Revision 0 schlägt fehl.
  • Ein anderer benannter Agent behält seinen isolierten Datensatz mit Revision 0.

Dieser deterministische Test beantwortet die Frage, ob die Operationen sicher sind. Die Modellauswahl wird nach der Bereitstellung separat demonstriert, da sie probabilistisch ist.

Eine begrenzte Tool-Runde bereitstellen und beobachten

In diesem Schritt stellen Sie die Anwendung bereit, prüfen die Grenzen erneut gegenüber Cloudflare und beobachten eine begrenzte Live-Modellrunde.

Stellen Sie das Produktions-Bundle bereit und laden Sie den erzeugten Signaturschlüssel als Secret hoch:

npm run deploy
npx wrangler secret bulk .dev.vars

Der Secret-Befehl überträgt den Wert, ohne ihn in der Konfiguration oder im Bundle abzulegen. Geben Sie .dev.vars nicht aus.

Speichern Sie die bei der Bereitstellung ausgegebene genaue Origin-URL und erstellen Sie anschließend ein zehn Minuten gültiges Token für planning:

WORKER_URL="https://paste-the-workers-dev-origin-printed-by-deploy"
TOKEN="$(node scripts/create-session-token.mjs planning)"
printf '%s/?session=planning&token=%s\n' "${WORKER_URL%/}" "$TOKEN"

Öffnen Sie die vollständige URL im LabEx-Browser. Senden Sie die vorbereitete Anfrage. Der Status wechselt zwischen submitted und streaming. Tool-Badges zeigen, dass das Modell einen Lese- und einen Aktualisierungsvorgang angefordert hat. Der abschließende Satz bestätigt die Priorität high mit der neuen Revision.

Die signierte planning-Sitzung nach der Ausführung validierter Lese- und Aktualisierungstools

Der genaue Wortlaut wird vom Modell erzeugt und kann abweichen. Warteschlange, Ticket und Datensatz sind synthetische Beispiele. Ein erfolgreicher Satz ist ein nützlicher Nachweis in der Benutzeroberfläche, aber nicht die maßgebliche Sicherheitsprüfung.

Senden Sie eine zweite Anfrage: Set T-SYNTH-101 to low using expected revision 0. Die veraltete Revision darf Revision 1 nicht stillschweigend überschreiben. Stattdessen sollte die Tool-Aktivität einen Konflikt anzeigen.

Eine vom serverseitigen Tool-Grenzbereich zurückgewiesene veraltete Revision

Führen Sie einen neuen Cloud-Test mit einem unabhängig benannten Agent aus. Dieser verbraucht keinen weiteren Modellaufruf:

python3 .labex/verify.py deployed

Der Test prüft die tatsächlich bereitgestellten Bindings und den Namespace. Anschließend wiederholt er gegenüber dem Remote-Worker die Zurückweisung fehlerhafter Schemas, die Bereichsprüfung, eine erfolgreiche Änderung der Revision, die Zurückweisung einer veralteten Wiederholung und die Isolation benannter Agents.

Die Tool-Ressourcen prüfen und entfernen

In diesem Schritt verknüpfen Sie das Laufzeitverhalten mit den Ressourcenansichten von Cloudflare und löschen anschließend ausschließlich die Ressourcen dieses Labs.

Öffnen Sie im Cloudflare-Dashboard Workers & Pages, wählen Sie Ihren exakten Worker labex-c11-s05-... aus und öffnen Sie Bindings. Dort sollten das Workers-AI-Binding AI und das Durable-Object-Binding SupportToolsAgent angezeigt werden. Öffnen Sie anschließend Settings > Variables and Secrets, um zu bestätigen, dass SESSION_SIGNING_KEY als verschlüsseltes Secret und nicht als Klartext gespeichert ist:

Der bereitgestellte Worker mit den Bindings AI und SupportToolsAgent

Öffnen Sie Durable Objects und wählen Sie den SQL-basierten Namespace aus, der diesem Worker gehört. planning und die Namen des Prüfers sind separate Objektinstanzen innerhalb dieses einen Klassen-Namespaces:

Der SQL-basierte SupportToolsAgent-Namespace

Öffnen Sie die Logs oder die Observability-Ansicht des Workers und suchen Sie nach tool_event. Der strukturierte Eintrag enthält den Tool-Namen, die Agent-Instanz und die Revision, aber weder die Zusammenfassung des synthetischen Falls noch den Chattext:

Ein begrenztes Ereignis des Aktualisierungstools in den Cloudflare-Logs

Erstellen Sie nach der Prüfung eine explizite Migration zum Löschen der Klasse und entfernen Sie den exakten Worker:

python3 - <<'PY'
import json
from pathlib import Path
path = Path('wrangler.jsonc')
data = json.loads(path.read_text())
data.pop('durable_objects', None)
data['migrations'].append({'tag': 'v2', 'deleted_classes': ['SupportToolsAgent']})
Path('wrangler.cleanup.jsonc').write_text(json.dumps(data, indent=2) + '\n')
PY
npx wrangler deploy --config wrangler.cleanup.jsonc
npx wrangler delete --config wrangler.cleanup.jsonc --force

Bestätigen Sie, dass der kurzlebige Worker nicht mehr vorhanden ist:

Der entfernte Worker für validierte Tools

Bestätigen Sie anschließend, dass sein SupportToolsAgent-Namespace nicht mehr vorhanden ist:

Der entfernte SupportToolsAgent-Namespace

Weisen Sie beide Abwesenheiten nach, solange diese VM noch autorisiert ist:

python3 .labex/verify.py deleted

Wenn Sie nur den Worker löschen, bleibt der Lebenszyklus der zustandsbehafteten Klasse unklar. Die Migration v2 entfernt den Namespace dieses Labs und seine synthetischen Datensätze ausdrücklich, bevor die Löschung des Workers geprüft wird.

Die Autorisierung dieser VM widerrufen

In diesem Schritt widerrufen Sie die vorübergehende VM-Autorisierung, nachdem die Bereinigung in der Cloud bestätigt wurde.

npx wrangler logout
npx wrangler whoami --json || true

Das strukturierte Ergebnis sollte "loggedIn": false melden. Alternativ kann Wrangler ein Ergebnis mit dem Rückgabecode ungleich null ausgeben, das eine fehlende Authentifizierung anzeigt. Die Abmeldung erfolgt absichtlich zuletzt: Der Löschtest benötigt gültigen Lesezugriff, während die verworfene VM ihn nicht mehr benötigt.

Zusammenfassung

Sie haben einem Cloudflare-AIChatAgent zwei begrenzte serverseitige Tools hinzugefügt. Sie haben:

  • strikte Zod-Verträge für einen Lesevorgang und eine synthetische Aktualisierung definiert;
  • die Autorisierung getrennt behandelt, indem der benannte Agent-Bereich auf dem Server erzwungen wird;
  • fehlerhafte Eingaben, Zugriffe auf andere Warteschlangen und veraltete Revisionen zurückgewiesen;
  • dieselbe Implementierung für Modell-Tools und deterministische callable-Tests wiederverwendet;
  • eine begrenzte Workers-AI-Tool-Runde sowie datenschutzbeschränkte Logs beobachtet;
  • den exakten SQLite-Klassen-Namespace und den Worker gelöscht, bevor Sie sich abgemeldet haben.

Diese Kontrollen machen eine direkte synthetische Aktualisierung klein und testbar. Sie verlangen jedoch noch nicht, dass eine Person die Wirkung freigibt. Im nächsten Lab fügen Sie diese Freigabegrenze hinzu und machen Genehmigung, Ablehnung und doppelte Zustellung explizit.