import { t as MessageType } from "./ai-types-DEtF_8Km.js";
import { use, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { usePartySocket } from "partysocket/react";
//#region src/react.tsx
/**
* Convert a camelCase string to a kebab-case string
* @param str The string to convert
* @returns The kebab-case string
*/
function camelCaseToKebabCase(str) {
if (str === str.toUpperCase() && str !== str.toLowerCase()) return str.toLowerCase().replace(/_/g, "-");
let kebabified = str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
kebabified = kebabified.startsWith("-") ? kebabified.slice(1) : kebabified;
return kebabified.replace(/_/g, "-").replace(/-$/, "");
}
const queryCache = /* @__PURE__ */ new Map();
function createCacheKey(agentNamespace, name, deps) {
return JSON.stringify([
agentNamespace,
name || "default",
...deps
]);
}
function getCacheEntry(key) {
const entry = queryCache.get(key);
if (!entry) return void 0;
if (Date.now() >= entry.expiresAt) {
queryCache.delete(key);
return;
}
return entry;
}
function setCacheEntry(key, promise, cacheTtl) {
const entry = {
promise,
expiresAt: Date.now() + cacheTtl
};
queryCache.set(key, entry);
return entry;
}
function deleteCacheEntry(key) {
queryCache.delete(key);
}
const _testUtils = {
queryCache,
setCacheEntry,
getCacheEntry,
deleteCacheEntry,
clearCache: () => queryCache.clear()
};
function useAgent(options) {
const agentNamespace = camelCaseToKebabCase(options.agent);
const { query, queryDeps, cacheTtl, ...restOptions } = options;
const pendingCallsRef = useRef(/* @__PURE__ */ new Map());
const cacheKey = useMemo(() => createCacheKey(agentNamespace, options.name, queryDeps || []), [
agentNamespace,
options.name,
queryDeps
]);
const ttl = cacheTtl ?? 300 * 1e3;
const [cacheInvalidatedAt, setCacheInvalidatedAt] = useState(0);
const queryPromise = useMemo(() => {
if (!query || typeof query !== "function") return null;
const cached = getCacheEntry(cacheKey);
if (cached) return cached.promise;
const promise = query().catch((error) => {
console.error(`[useAgent] Query failed for agent "${options.agent}":`, error);
deleteCacheEntry(cacheKey);
throw error;
});
setCacheEntry(cacheKey, promise, ttl);
return promise;
}, [
cacheKey,
query,
options.agent,
ttl,
cacheInvalidatedAt
]);
useEffect(() => {
if (!queryPromise || ttl <= 0) return;
const entry = getCacheEntry(cacheKey);
if (!entry) return;
const timeUntilExpiry = entry.expiresAt - Date.now();
const timer = setTimeout(() => {
deleteCacheEntry(cacheKey);
setCacheInvalidatedAt(Date.now());
}, Math.max(0, timeUntilExpiry));
return () => clearTimeout(timer);
}, [
cacheKey,
queryPromise,
ttl
]);
let resolvedQuery;
if (query) if (typeof query === "function") {
const queryResult = use(queryPromise);
if (queryResult) {
for (const [key, value] of Object.entries(queryResult)) if (value !== null && value !== void 0 && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") console.warn(`[useAgent] Query parameter "${key}" is an object and will be converted to "[object Object]". Query parameters should be string, number, boolean, or null.`);
resolvedQuery = queryResult;
}
} else resolvedQuery = query;
const agent = usePartySocket({
party: agentNamespace,
prefix: "agents",
room: options.name || "default",
query: resolvedQuery,
...restOptions,
onMessage: (message) => {
if (typeof message.data === "string") {
let parsedMessage;
try {
parsedMessage = JSON.parse(message.data);
} catch (_error) {
return options.onMessage?.(message);
}
if (parsedMessage.type === MessageType.CF_AGENT_STATE) {
options.onStateUpdate?.(parsedMessage.state, "server");
return;
}
if (parsedMessage.type === MessageType.CF_AGENT_MCP_SERVERS) {
options.onMcpUpdate?.(parsedMessage.mcp);
return;
}
if (parsedMessage.type === MessageType.RPC) {
const response = parsedMessage;
const pending = pendingCallsRef.current.get(response.id);
if (!pending) return;
if (!response.success) {
pending.reject(new Error(response.error));
pendingCallsRef.current.delete(response.id);
pending.stream?.onError?.(response.error);
return;
}
if ("done" in response) if (response.done) {
pending.resolve(response.result);
pendingCallsRef.current.delete(response.id);
pending.stream?.onDone?.(response.result);
} else pending.stream?.onChunk?.(response.result);
else {
pending.resolve(response.result);
pendingCallsRef.current.delete(response.id);
}
return;
}
}
options.onMessage?.(message);
}
});
const call = useCallback((method, args = [], streamOptions) => {
return new Promise((resolve, reject) => {
const id = Math.random().toString(36).slice(2);
pendingCallsRef.current.set(id, {
reject,
resolve,
stream: streamOptions
});
const request = {
args,
id,
method,
type: MessageType.RPC
};
agent.send(JSON.stringify(request));
});
}, [agent]);
agent.setState = (state) => {
agent.send(JSON.stringify({
state,
type: MessageType.CF_AGENT_STATE
}));
options.onStateUpdate?.(state, "client");
};
agent.call = call;
agent.agent = agentNamespace;
agent.name = options.name || "default";
agent.stub = new Proxy({}, { get: (_target, method) => {
return (...args) => {
return call(method, args);
};
} });
if (agent.agent !== agent.agent.toLowerCase()) console.warn(`Agent name: ${agent.agent} should probably be in lowercase. Received: ${agent.agent}`);
return agent;
}
//#endregion
export { _testUtils, useAgent };
//# sourceMappingURL=react.js.map