agents
Version:
A home for your AI agents
608 lines (607 loc) • 25 kB
JavaScript
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "../classPrivateFieldGet2-DZBYAB34.js";
import { t as _classPrivateMethodInitSpec } from "../classPrivateMethodInitSpec-qMjJ6sHQ.js";
import { t as isChannelMessageSurface } from "../surface-bZZJqBka.js";
import { c as unsupported, t as compositeDestinations } from "../internal-CYlgHl1l.js";
import { n as collectText, r as consumeChunks, t as matchesPath } from "../ingress-BfetZbMO.js";
//#region src/channels/fallback.ts
/**
* Build an inert fallback destination for a `ChannelHost` to resolve.
*
* The Host skips unavailable destinations, advances after confirmed failures,
* and stops after a delivered or uncertain result to avoid duplicates.
*/
function fallback(surfaces) {
return {
channelKey: "fallback",
version: 1,
address: { surfaces },
label: surfaces.map((surface) => surface.label).join(", then ")
};
}
/**
* Advance only when a failed destination has not started reading the answer.
*
* Replaying an arbitrarily large consumed prefix requires an arbitrarily large
* buffer. Instead, each attempt gets a cancellation-shielded view of the same
* source. Once an attempt asks for its first chunk, its result is terminal and
* the source is never handed to another destination.
*/
async function streamWithFallbackBeforeRead(resolve, destinations, chunks, options) {
const reader = chunks.getReader();
let drained = false;
function attempt() {
let startedReading = false;
return {
chunks: new ReadableStream({
async pull(controller) {
startedReading = true;
const result = await reader.read();
if (result.done) {
drained = true;
controller.close();
return;
}
controller.enqueue(result.value);
},
cancel() {}
}, { highWaterMark: 0 }),
startedReading: () => startedReading
};
}
try {
for (let index = 0; index < destinations.length - 1; index += 1) {
const destination = destinations[index];
if (!await resolve.isAvailable(destination)) continue;
const current = attempt();
const result = await resolve.stream(destination, current.chunks, options);
if (result.status !== "failed" || current.startedReading()) return result;
}
const final = attempt();
return await resolve.stream(destinations.at(-1), final.chunks, options);
} finally {
if (!drained) await reader.cancel().catch(() => {});
reader.releaseLock();
}
}
/** Build the ordinary Channel installed under the reserved fallback key. */
function fallbackChannel(resolve) {
async function run(surface, operation) {
const destinations = compositeDestinations(surface);
if (!destinations) return unsupported("FALLBACK_SURFACE_INVALID", "Fallback surface must contain at least one valid destination");
for (let index = 0; index < destinations.length - 1; index += 1) {
const destination = destinations[index];
if (await resolve.isAvailable(destination)) {
const result = await operation(destination);
if (result.status !== "failed") return result;
}
}
return operation(destinations.at(-1));
}
return {
deliver(surface, message, options) {
return run(surface, (destination) => resolve.deliver(destination, message, options));
},
async stream(surface, chunks, options) {
const destinations = compositeDestinations(surface);
if (!destinations) {
await chunks.cancel().catch(() => {});
return unsupported("FALLBACK_SURFACE_INVALID", "Fallback surface must contain at least one valid destination");
}
return streamWithFallbackBeforeRead(resolve, destinations, chunks, options);
},
requestApproval(surface, options) {
return run(surface, (destination) => resolve.requestApproval(destination, options));
},
async isAvailable(surface) {
const destinations = compositeDestinations(surface);
if (!destinations) return true;
for (const destination of destinations) if (await resolve.isAvailable(destination)) return true;
return false;
}
};
}
//#endregion
//#region src/channels/fanout.ts
function teeAll(source, count) {
const branches = [];
let rest = source;
for (let index = 0; index < count - 1; index += 1) {
const [branch, remainder] = rest.tee();
branches.push(branch);
rest = remainder;
}
branches.push(rest);
return branches;
}
/** Build an inert fanout destination for a `ChannelHost` to resolve. */
function fanout(surfaces) {
return {
channelKey: "fanout",
version: 1,
address: { surfaces },
label: surfaces.map((surface) => surface.label).join(" and ")
};
}
/** Build the ordinary Channel installed under the reserved fanout key. */
function fanoutChannel(resolve) {
async function run(surface, operation) {
const destinations = compositeDestinations(surface);
if (!destinations) return unsupported("FANOUT_SURFACE_INVALID", "Fanout surface must contain at least one valid destination");
return combine(await Promise.all(destinations.map(operation)));
}
function combine(results) {
if (results.every((result) => result.status === "delivered")) return { status: "delivered" };
if (results.every((result) => result.status === "failed")) return {
status: "failed",
retryable: results.every((result) => result.status === "failed" && result.retryable),
error: {
code: "FANOUT_DELIVERY_FAILED",
message: "Every fanout destination rejected the delivery"
}
};
return {
status: "uncertain",
error: {
code: "FANOUT_DELIVERY_UNCERTAIN",
message: "Fanout delivery was partial or had an uncertain destination outcome"
}
};
}
return {
deliver(surface, message, options) {
return run(surface, (destination) => resolve.deliver(destination, message, options));
},
async stream(surface, chunks, options) {
const destinations = compositeDestinations(surface);
if (!destinations) {
await chunks.cancel().catch(() => {});
return unsupported("FANOUT_SURFACE_INVALID", "Fanout surface must contain at least one valid destination");
}
const branches = teeAll(chunks, destinations.length);
return combine(await Promise.all(destinations.map(async (destination, index) => {
const branch = branches[index];
try {
return await resolve.stream(destination, branch, options);
} finally {
branch.cancel().catch(() => {});
}
})));
},
requestApproval(surface, options) {
return run(surface, (destination) => resolve.requestApproval(destination, options));
},
async isAvailable(surface) {
const destinations = compositeDestinations(surface);
if (!destinations) return true;
return (await Promise.all(destinations.map((destination) => resolve.isAvailable(destination)))).every(Boolean);
}
};
}
//#endregion
//#region src/channels/host/index.ts
const POLICY_KEYS = /* @__PURE__ */ new Set(["fallback", "fanout"]);
var _channels = /* @__PURE__ */ new WeakMap();
var _defaultRoute = /* @__PURE__ */ new WeakMap();
var _findUser = /* @__PURE__ */ new WeakMap();
var _onRoute = /* @__PURE__ */ new WeakMap();
var _onMessage = /* @__PURE__ */ new WeakMap();
var _onApprovalResponse = /* @__PURE__ */ new WeakMap();
var _ChannelHost_brand = /* @__PURE__ */ new WeakSet();
/**
* Authenticates and normalizes ingress through configured Channel adapters,
* resolves outbound surfaces, and awaits the application's durable handoff.
*/
var ChannelHost = class {
constructor(options) {
_classPrivateMethodInitSpec(this, _ChannelHost_brand);
_classPrivateFieldInitSpec(this, _channels, void 0);
_classPrivateFieldInitSpec(this, _defaultRoute, void 0);
_classPrivateFieldInitSpec(this, _findUser, void 0);
_classPrivateFieldInitSpec(this, _onRoute, void 0);
_classPrivateFieldInitSpec(this, _onMessage, void 0);
_classPrivateFieldInitSpec(this, _onApprovalResponse, void 0);
for (const channelKey of Object.keys(options.channels)) if (POLICY_KEYS.has(channelKey)) throw new Error(`Channel key "${channelKey}" is reserved for a delivery policy`);
const channels = { ...options.channels };
_classPrivateFieldSet2(_channels, this, channels);
channels.fallback = fallbackChannel(this);
channels.fanout = fanoutChannel(this);
_classPrivateFieldSet2(_defaultRoute, this, options.defaultRoute);
_classPrivateFieldSet2(_findUser, this, options.findUser);
_classPrivateFieldSet2(_onRoute, this, options.onRoute);
_classPrivateFieldSet2(_onMessage, this, options.onMessage);
_classPrivateFieldSet2(_onApprovalResponse, this, options.onApprovalResponse);
}
async handleRequest(request) {
for (const [channelKey, channel] of Object.entries(_classPrivateFieldGet2(_channels, this))) {
const ingress = channel.ingress;
if (!ingress) continue;
try {
const result = await ingress.receive(request);
if (!result) continue;
for (const envelope of result.events) await _assertClassBrand(_ChannelHost_brand, this, _dispatch).call(this, channelKey, channel, envelope);
return result.response;
} catch {
return new Response("Failed to handle Channel event", { status: 500 });
}
}
}
async handleEmail(email) {
for (const [channelKey, channel] of Object.entries(_classPrivateFieldGet2(_channels, this))) {
const ingress = channel.emailIngress;
if (!ingress) continue;
const result = await ingress.receive(email);
if (!result) continue;
for (const envelope of result.events) await _assertClassBrand(_ChannelHost_brand, this, _dispatch).call(this, channelKey, channel, envelope);
return true;
}
return false;
}
/** Deliver through the configured Channel or composite named by the surface. */
deliver(surface, message, options) {
return _assertClassBrand(_ChannelHost_brand, this, _outbound).call(this, surface, (channel, destination) => {
if (!channel.deliver) return Promise.resolve(unsupported("CHANNEL_DELIVERY_UNSUPPORTED", `Channel "${destination.channelKey}" does not support delivery`));
return channel.deliver(destination, message, options);
});
}
/**
* Deliver a progressively generated answer to the Channel or composite
* named by the surface.
*
* A Channel that can stream consumes the stream itself. A Channel that
* cannot never learns it was a stream, because the Host collects the answer
* and calls `deliver` once.
*/
async stream(surface, chunks, options = {}) {
if (!isChannelMessageSurface(surface)) {
await chunks.cancel().catch(() => {});
return invalidSurface();
}
const channel = _assertClassBrand(_ChannelHost_brand, this, _configuredChannel).call(this, surface.channelKey);
if (!channel.stream && !channel.deliver) {
await chunks.cancel().catch(() => {});
return unsupported("CHANNEL_DELIVERY_UNSUPPORTED", `Channel "${surface.channelKey}" does not support delivery`);
}
if (channel.stream) return channel.stream(surface, chunks, options);
return collectAndDeliver(channel, surface, chunks, options);
}
/** Request approval through the Channel or composite named by the surface. */
requestApproval(surface, options) {
return _assertClassBrand(_ChannelHost_brand, this, _outbound).call(this, surface, (channel, destination) => {
if (!channel.requestApproval) return Promise.resolve(unsupported("CHANNEL_APPROVAL_UNSUPPORTED", `Channel "${destination.channelKey}" does not support approval requests`));
return channel.requestApproval(destination, options);
});
}
/** Return the identity's configured Channel destination, when supported. */
contactSurface(identity) {
const surface = (Object.prototype.hasOwnProperty.call(_classPrivateFieldGet2(_channels, this), identity.channelKey) ? _classPrivateFieldGet2(_channels, this)[identity.channelKey] : void 0)?.contactSurface?.(identity);
return surface ? stampSurface(identity.channelKey, surface) : null;
}
/** Resolve whether a surface can currently be selected without delivery. */
async isAvailable(surface) {
if (!isChannelMessageSurface(surface)) return false;
return _assertClassBrand(_ChannelHost_brand, this, _configuredChannel).call(this, surface.channelKey).isAvailable?.(surface) ?? true;
}
};
async function _outbound(surface, operation) {
if (!isChannelMessageSurface(surface)) return invalidSurface();
return operation(_assertClassBrand(_ChannelHost_brand, this, _configuredChannel).call(this, surface.channelKey), surface);
}
function _configuredChannel(channelKey) {
const channel = Object.prototype.hasOwnProperty.call(_classPrivateFieldGet2(_channels, this), channelKey) ? _classPrivateFieldGet2(_channels, this)[channelKey] : void 0;
if (!channel) throw new Error(`Channel message surface names unknown configured Channel key "${channelKey}"`);
return channel;
}
async function _dispatch(channelKey, channel, envelope) {
const rawEvent = envelope.event;
const event = stampEvent(channelKey, rawEvent);
const route = await _assertClassBrand(_ChannelHost_brand, this, _route).call(this, channelKey, channel, event, envelope.raw);
const dispatchId = await createDispatchId(channelKey, event.eventId);
await _classPrivateFieldGet2(_onRoute, this)?.call(this, {
channelKey,
event,
route,
dispatchId
});
if (route === null) return;
if (event.type === "message") {
if (!_classPrivateFieldGet2(_onMessage, this)) throw new Error(`Channel "${channelKey}" received a message without an onMessage callback`);
await _classPrivateFieldGet2(_onMessage, this).call(this, {
channelKey,
route,
dispatchId,
message: event
});
return;
}
if (!_classPrivateFieldGet2(_onApprovalResponse, this)) throw new Error(`Channel "${channelKey}" received an approval response without an onApprovalResponse callback`);
await _classPrivateFieldGet2(_onApprovalResponse, this).call(this, {
channelKey,
route,
dispatchId,
response: event
});
}
async function _route(channelKey, channel, event, raw) {
const context = _assertClassBrand(_ChannelHost_brand, this, _routeContext).call(this, event);
const route = channel.route ? await channel.route(event, raw, context) : _classPrivateFieldGet2(_defaultRoute, this) ? await _classPrivateFieldGet2(_defaultRoute, this).call(this, event, raw, context) : event.thread.id;
if (route === void 0) throw new Error(`Channel route for "${channelKey}" returned undefined; return null to ignore an event`);
if (route !== null && typeof route !== "string") throw new Error(`Channel route for "${channelKey}" must return a string or null`);
return route;
}
function _routeContext(event) {
let linkedUser;
return { findUser: () => {
if (!linkedUser) {
const identity = event.actor?.identity;
const findUser = _classPrivateFieldGet2(_findUser, this);
linkedUser = identity && findUser ? Promise.resolve().then(() => findUser(identity)) : Promise.resolve(null);
}
return linkedUser;
} };
}
/**
* Serve a Channel that cannot stream by collecting the answer first.
*
* A generation that failed part-way still delivers what it produced, because
* losing the partial answer helps nobody, but the result is downgraded to
* `uncertain` since the reader received an incomplete answer.
*/
function invalidSurface() {
return unsupported("CHANNEL_SURFACE_INVALID", "Cannot resolve an invalid Channel message surface");
}
async function collectAndDeliver(channel, surface, stream, options) {
const collected = await collectText(stream);
if (collected.interrupted && collected.text.length === 0) return {
status: "failed",
retryable: false,
error: {
code: "CHANNEL_STREAM_INTERRUPTED",
message: "The stream ended before producing any content to deliver"
}
};
const result = await channel.deliver(surface, {
...options.title !== void 0 && { title: options.title },
markdown: collected.text
}, options.delivery ? { delivery: options.delivery } : void 0);
if (!collected.interrupted || result.status !== "delivered") return result;
return {
status: "uncertain",
...result.reference !== void 0 && { reference: result.reference },
error: {
code: "CHANNEL_STREAM_INTERRUPTED",
message: "An incomplete answer was delivered because the stream ended early"
}
};
}
function stampSurface(channelKey, surface) {
return {
...surface,
channelKey
};
}
function stampIdentity(channelKey, identity) {
return {
...identity,
channelKey
};
}
function stampEvent(channelKey, event) {
return {
...event,
...event.replySurface && { replySurface: stampSurface(channelKey, event.replySurface) },
...event.actor && { actor: {
...event.actor,
...event.actor.identity && { identity: stampIdentity(channelKey, event.actor.identity) }
} }
};
}
/** Hash an unambiguous tuple so dispatch identities remain safe to carry. */
async function createDispatchId(channelKey, eventId) {
const identity = new TextEncoder().encode(JSON.stringify([channelKey, eventId]));
const digest = await crypto.subtle.digest("SHA-256", identity);
return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
}
//#endregion
//#region src/channels/identity.ts
const DEFAULT_IDENTITY_SCOPE = "default";
function normalizeIdentity(identity) {
return {
channelKey: identity.channelKey,
scope: identity.scope ?? DEFAULT_IDENTITY_SCOPE,
subject: identity.subject
};
}
/** Build a stable key for comparing or indexing a Channel identity. */
function identityKey(identity) {
const normalized = normalizeIdentity(identity);
return JSON.stringify([
normalized.channelKey,
normalized.scope,
normalized.subject
]);
}
/** Raised when an operation would join identities already assigned to users. */
var UserIdentityConflictError = class extends Error {
constructor(conflicts, attemptedUserId) {
super(attemptedUserId ? `A channel identity is already linked to a different user than ${attemptedUserId}` : "Both channel identities are already linked to users");
this.conflicts = conflicts;
this.attemptedUserId = attemptedUserId;
this.code = "USER_IDENTITY_CONFLICT";
this.name = "UserIdentityConflictError";
}
};
const USERS_TABLE = "cf_channels_users_v1";
const LINKS_TABLE = "cf_channels_user_identity_links_v1";
function requireNonEmpty(label, value) {
if (value.length === 0) throw new TypeError(`${label} must be a non-empty string`);
}
function validatedIdentity(identity) {
const normalized = normalizeIdentity(identity);
requireNonEmpty("identity.channelKey", normalized.channelKey);
requireNonEmpty("identity.scope", normalized.scope);
requireNonEmpty("identity.subject", normalized.subject);
return normalized;
}
function sameIdentity(first, second) {
return identityKey(first) === identityKey(second);
}
function compareIdentities(first, second) {
return first.channelKey.localeCompare(second.channelKey) || first.scope.localeCompare(second.scope) || first.subject.localeCompare(second.subject);
}
/** Build the off-the-shelf user identity store over application-owned SQLite. */
function createUserIdentityStore(storage, options = {}) {
const createUserId = options.createUserId ?? (() => crypto.randomUUID());
storage.sql.exec(`
CREATE TABLE IF NOT EXISTS ${USERS_TABLE} (
user_id TEXT PRIMARY KEY
) WITHOUT ROWID
`);
storage.sql.exec(`
CREATE TABLE IF NOT EXISTS ${LINKS_TABLE} (
channel_key TEXT NOT NULL,
scope TEXT NOT NULL,
subject TEXT NOT NULL,
user_id TEXT NOT NULL,
PRIMARY KEY (channel_key, scope, subject)
) WITHOUT ROWID
`);
storage.sql.exec(`
CREATE INDEX IF NOT EXISTS ${LINKS_TABLE}_user
ON ${LINKS_TABLE} (user_id, channel_key, scope, subject)
`);
function linkedUserId(identity) {
return storage.sql.exec(`SELECT user_id FROM ${LINKS_TABLE}
WHERE channel_key = ? AND scope = ? AND subject = ?`, identity.channelKey, identity.scope, identity.subject).toArray()[0]?.user_id ?? null;
}
function readUser(userId) {
const user = storage.sql.exec(`SELECT user_id FROM ${USERS_TABLE} WHERE user_id = ?`, userId).toArray()[0];
if (!user) return null;
const channelIdentities = storage.sql.exec(`SELECT channel_key AS channelKey, scope, subject FROM ${LINKS_TABLE}
WHERE user_id = ?
ORDER BY channel_key, scope, subject`, userId).toArray().sort(compareIdentities);
return {
id: user.user_id,
channelIdentities
};
}
function insertUser(userId) {
return storage.sql.exec(`INSERT INTO ${USERS_TABLE} (user_id) VALUES (?)
ON CONFLICT (user_id) DO NOTHING
RETURNING user_id`, userId).toArray()[0] !== void 0;
}
function createUser() {
for (let attempt = 0; attempt < 10; attempt += 1) {
const userId = createUserId();
requireNonEmpty("createUserId() result", userId);
if (insertUser(userId)) return userId;
}
throw new Error("createUserId() repeatedly returned existing user IDs");
}
function insertLink(userId, identity) {
storage.sql.exec(`INSERT INTO ${LINKS_TABLE} (channel_key, scope, subject, user_id)
VALUES (?, ?, ?, ?)`, identity.channelKey, identity.scope, identity.subject, userId);
}
return {
async link(userId, identity) {
requireNonEmpty("userId", userId);
const normalized = validatedIdentity(identity);
return storage.transactionSync(() => {
const existingUserId = linkedUserId(normalized);
if (existingUserId && existingUserId !== userId) throw new UserIdentityConflictError([{
channelIdentity: normalized,
userId: existingUserId
}], userId);
insertUser(userId);
if (!existingUserId) insertLink(userId, normalized);
const user = readUser(userId);
if (!user) throw new Error("Linked user identity could not be read");
return user;
});
},
async findUser(identity) {
const userId = linkedUserId(validatedIdentity(identity));
return userId ? readUser(userId) : null;
},
async getUser(userId) {
requireNonEmpty("userId", userId);
return readUser(userId);
},
async listUsers() {
return storage.sql.exec(`SELECT user_id FROM ${USERS_TABLE} ORDER BY user_id`).toArray().map(({ user_id }) => readUser(user_id)).filter((user) => user !== null);
},
async linkChannelIdentities(first, second) {
const normalizedFirst = validatedIdentity(first);
const normalizedSecond = validatedIdentity(second);
if (sameIdentity(normalizedFirst, normalizedSecond)) throw new TypeError("Channel identities must be distinct");
return storage.transactionSync(() => {
const firstUserId = linkedUserId(normalizedFirst);
const secondUserId = linkedUserId(normalizedSecond);
if (firstUserId && secondUserId) throw new UserIdentityConflictError([{
channelIdentity: normalizedFirst,
userId: firstUserId
}, {
channelIdentity: normalizedSecond,
userId: secondUserId
}]);
const userId = firstUserId ?? secondUserId ?? createUser();
if (!firstUserId) insertLink(userId, normalizedFirst);
if (!secondUserId) insertLink(userId, normalizedSecond);
const user = readUser(userId);
if (!user) throw new Error("Linked user identity could not be read");
return user;
});
}
};
}
/** Join two Channel identities through a `UserIdentityStore`. */
function linkChannelIdentities(store, first, second) {
return store.linkChannelIdentities(first, second);
}
//#endregion
//#region src/channels/routes.ts
/**
* Common deterministic routing policies for normalized Channel events.
*
* Each one maps an event to a namespaced application route. Deciding whether
* an event is relevant at all is application policy: write that in your own
* `route` function and return `null` to ignore the event.
*/
const routes = {
/**
* Give every event its own application route. Useful to kick off a
* new conversation for each ingress, and where you don't want subsequent
* messages routed back to that same conversation.
**/
perEvent(event) {
return `event:${event.eventId}`;
},
/** Send every event in the same thread to the same conversation */
perThread(event) {
return `thread:${event.thread.id}`;
},
/**
* Prefer the sender's own channel identity, then delegate to another route
* policy. Without a fallback, an event carrying no identity is ignored.
*
* This groups events that carry the *same* identity. It never infers that
* two different identities belong to one person: that is an explicit
* application decision, exposed to routing through `byUser`.
*/
byIdentity(fallback) {
return (event, raw, context) => {
const identity = event.actor?.identity;
if (identity) return `identity:${identityKey(identity)}`;
return fallback ? fallback(event, raw, context) : null;
};
},
/** Prefer an explicitly linked user, then delegate to another route policy. */
byUser(fallback) {
return async (event, raw, context) => {
const user = await context.findUser();
return user ? `user:${user.id}` : fallback(event, raw, context);
};
}
};
//#endregion
export { ChannelHost, UserIdentityConflictError, consumeChunks, createUserIdentityStore, fallback, fallbackChannel, fanout, fanoutChannel, identityKey, isChannelMessageSurface, linkChannelIdentities, matchesPath, routes };
//# sourceMappingURL=index.js.map