INTEGRITY Документация

Bindings

Привязки позволяют контролировать, к чему может обращаться Dynamic Worker. Создавая Dynamic Worker, вы точно определяете, какие ресурсы и операции ему доступны.

Это позволяет:

Пользовательские привязки в Dynamic Worker

С пользовательскими привязками вы можете:

Как это работает

Шаг 1. Определите привязку

Чтобы создать пользовательскую привязку, ваш loader Worker должен реализовать WorkerEntrypoint класс и экспортируйте его. Методы, которые вы определите в этом классе, и есть те методы, которые сможет вызывать Dynamic Worker.

import { WorkerEntrypoint } from "cloudflare:workers";

export class ChatRoom extends WorkerEntrypoint {
  async post(text: string): Promise<void> {
    // Your implementation here
  }
}

Шаг 2. Передайте ее в Dynamic Worker

Затем ваш loader Worker создаст экземпляр экспортированного класса, называемый заглушкой, и передаст его в Dynamic Worker env.

let chatRoom = ctx.exports.ChatRoom({ props: { roomName: "#bot-chat" } });

let worker = env.LOADER.load({
  env: { CHAT_ROOM: chatRoom },
  // ...
});

С точки зрения Dynamic Worker, CHAT_ROOM выглядит как обычная привязка с методами, которые можно вызвать:

// Inside the Dynamic Worker
await this.env.CHAT_ROOM.post("Hello!");

Шаг 3. Настройте для каждого пользователя с помощью props

Один класс может обслуживать множество разных Dynamic Workers. Вместо того чтобы определять отдельный класс для каждого пользователя, вы передаете props при создании stub, который содержит информацию, специфичную для этого пользователя.

// Same class, different props per user
let aliceRoom = ctx.exports.ChatRoom({ props: { roomName: "#alice", apiKey: ALICE_KEY } });
let bobRoom   = ctx.exports.ChatRoom({ props: { roomName: "#bob", apiKey: BOB_KEY } });

Когда Dynamic Worker вызывает метод у привязки, он фактически делает обратный вызов вашему loader Worker: именно там этот метод и выполняется. Внутри этого метода вы можете прочитать props через this.ctx.props. Доступ к props есть только у Worker-загрузчика, Dynamic Worker их никогда не видит.

export class ChatRoom extends WorkerEntrypoint<Cloudflare.Env, ChatRoomProps> {
  async post(text: string): Promise<void> {
    // Props are set when the stub is created — the Dynamic Worker never sees them
    let roomName = this.ctx.props.roomName;
    await postToChat(roomName, text);
  }
}

Пример: агент чат-комнаты

Вот полный пример, объединяющий всё вместе. Допустим, вы создаёте платформу, на которой ИИ-агенты могут отправлять сообщения в чат-комнаты. Каждый агент должен иметь возможность писать только в назначенную ему комнату и никогда не должен видеть ключ API, используемый для аутентификации.

Вы определяете ChatRoom класс в вашем родительском Worker. В этом классе есть post метод, единственный метод, который Dynamic Worker может вызвать у этой привязки. Внутри этого класса вы определяете, в какую комнату отправляется сообщение, какой API-ключ используется и какое имя прикрепляется к сообщению.

import { WorkerEntrypoint } from "cloudflare:workers";

export class ChatRoom extends WorkerEntrypoint<Cloudflare.Env, ChatRoomProps> {
  async post(text: string): Promise<void> {
    let { apiKey, botName, roomName } = this.ctx.props;

    // Prefix the message with the bot's name.
    text = `[${botName}]: ${text}`;

    // Send it to the chat service.
    await postToChat(apiKey, roomName, text);
  }
}

type ChatRoomProps = {
  apiKey: string;
  roomName: string;
  botName: string;
};

Вы экспортируете один ChatRoom класс, но каждый созданный вами stub может иметь разные props : другое имя комнаты, другой API-ключ, другое имя бота. props задаются при создании stub, и Dynamic Worker их никогда не видит.

Теперь передайте его в Dynamic Worker:

// Create a stub scoped to a specific room.
let chatRoom = ctx.exports.ChatRoom({
  props: {
    apiKey,
    roomName: "#bot-chat",
    botName: "Robo",
  },
});

let worker = env.LOADER.load({
  env: {
    CHAT_ROOM: chatRoom,
  },
  compatibilityDate: "$today",
  mainModule: "index.js",
  modules: {
    "index.js": `
      export class Agent extends WorkerEntrypoint {
        async run() {
          // This is all the Dynamic Worker sees.
          await this.env.CHAT_ROOM.post("Hello!");
        }
      }
    `,
  },
  globalOutbound: null,
});

return worker.getEntrypoint("Agent").run();

Агент просто вызывает this.env.CHAT_ROOM.post("Hello!"). У него нет возможности отправлять сообщения в другую комнату, видеть или использовать API-ключ, или менять имя бота, прикреплённое к его сообщениям.

Совет: расскажите своему агенту о типах

Чтобы ИИ-агент мог писать код для ваших привязок, ему нужно знать интерфейс. Предоставьте агенту объявления типов TypeScript с комментариями-документацией, описывающими каждый метод. Современные LLM хорошо понимают TypeScript, поэтому это самый компактный способ описать JavaScript API. Это работает, даже если агент пишет обычный JavaScript.

Убедитесь, что ваш WorkerEntrypoint класс расширяет тип TypeScript, чтобы объявления не расходились с реализацией.

Передача обычных привязок Workers

Чтобы передать такие ресурсы, как KV пространство имён или R2 bucket для Dynamic Worker, нужно привязать ресурс к вашему Worker-загрузчику и создать пользовательскую привязку, которая его оборачивает. Вы можете ограничивать доступ для каждого клиента, добавляя префикс к ключам и определяя только те методы, которые хотите открыть.

Пример: разделение пространства имён KV по клиентам

Сначала привяжите пространство имён KV к своему Worker-загрузчику. Затем в Worker-загрузчике экспортируйте класс, который использует привязку KV и определяет методы, доступные для вызова из Dynamic Workers:

import { WorkerEntrypoint } from "cloudflare:workers";

export class MyStorage extends WorkerEntrypoint<Cloudflare.Env, MyStorageProps> {
  // Export this class from your loader Worker
  // The Dynamic Worker will be able to call get() and put()
  async get(key: string): Promise<string | null> {
    // Prefix the key so this customer can only access their own data
    return this.env.MY_KV.get(`${this.ctx.props.prefix}:${key}`);
  }

  async put(key: string, value: string): Promise<void> {
    await this.env.MY_KV.put(`${this.ctx.props.prefix}:${key}`, value);
  }
}

type MyStorageProps = {
  prefix: string;
};

Затем передайте его в Dynamic Worker с префиксом для конкретного клиента:

// Create a stub scoped to this customer's prefix
let storage = ctx.exports.MyStorage({
  props: { prefix: `customer-${customerId}` },
});

let worker = env.LOADER.load({
  env: { STORAGE: storage },
  // ...
});

Dynamic Worker просто использует ее как любую другую привязку:

// Inside the Dynamic Worker, it just sees STORAGE with get and put
let value = await this.env.STORAGE.get("settings");
await this.env.STORAGE.put("settings", "dark-mode");

Тот же паттерн работает для любого ресурса, к которому у вашего loader Worker есть доступ: R2 buckets или D1 базы данных. Привяжите ресурс к вашему Worker-загрузчику, экспортируйте класс, который его использует, и передайте stub в Dynamic Worker.

Сведения о постоянном хранилище, привязанном к каждому Dynamic Worker, см. в Фасеты Durable Object.

Песочницы на основе возможностей

Пользовательские привязки следуют модели безопасности на основе возможностей: Dynamic Worker может получить доступ только к тому, что вы явно ему предоставили. Если он не получил stub для чего-либо, он не сможет к этому обратиться.

В основе этого лежит Workers RPC, также известный как Cap'n Web ↗, система RPC, предназначенная для передачи ссылок на объекты через границы безопасности. Когда Dynamic Worker получает стаб, он может вызывать методы этого объекта, и каждый такой вызов становится RPC-запросом обратно к исходному объекту в вашем Worker-загрузчике. У стаба нет глобального идентификатора, и подделать его нельзя: единственный способ получить стаб, это чтобы его вам передали.

Безопасность на основе возможностей лежит в основе большинства успешных песочниц, хотя обычно она скрыта как деталь реализации: у Android есть Binder, у Chrome есть Mojo, а у Cloudflare Workers есть Cap'n Web. Dynamic Worker напрямую предоставляют эту возможность вам, разработчику, чтобы вы могли построить собственную надежную песочницу.