gemikenwork / runtime-dist /runtime /agentProvider.js
burtenshaw's picture
burtenshaw HF Staff
Deploy public WorkAdventure agent town
8eaf022 verified
Raw History Blame Contribute Delete
8.23 kB
const DEFAULT_MAX_ACTIONS = 20;
const DEFAULT_MAX_TEXT_LENGTH = 2000;
export const availableProviderActions = [
"say",
"startTyping",
"stopTyping",
"moveTo",
"teleport",
"setStatus",
"emitEvent",
"interruptAudioStream",
];
export function normalizeProviderActions(value, options = {}) {
const maxActions = options.maxActions ?? DEFAULT_MAX_ACTIONS;
if (Array.isArray(value)) {
const actions = [];
for (const entry of value.slice(0, maxActions)) {
const action = normalizeProviderAction(entry, options);
if (action) {
actions.push(action);
}
}
return actions;
}
if (isRecord(value)) {
if (Array.isArray(value.actions)) {
return normalizeProviderActions(value.actions, options);
}
const action = normalizeProviderAction(value, options);
return action ? [action] : [];
}
return [];
}
export function actionsFromProviderResponse(value, options = {}) {
const normalized = normalizeProviderActions(value, options);
if (normalized.length > 0) {
return normalized;
}
const responseText = extractResponseText(value);
if (!responseText) {
return [];
}
const actionsFromText = extractActionsFromText(responseText, options);
if (actionsFromText.length > 0) {
return actionsFromText;
}
return [
{
type: "say",
channel: options.defaultChannel ?? "bubble",
text: truncateText(responseText, options.maxTextLength ?? DEFAULT_MAX_TEXT_LENGTH),
},
];
}
export function extractActionsFromText(text, options = {}) {
const candidates = jsonCandidatesFromText(text);
for (const candidate of candidates) {
try {
const parsed = JSON.parse(candidate);
const actions = normalizeProviderActions(parsed, options);
if (actions.length > 0) {
return actions;
}
}
catch (_error) {
// Try the next candidate.
}
}
return [];
}
export function shouldRespondToChat(observation, names, respondToAllBubbleMessages) {
if (observation.type !== "chatMessage" || observation.scope !== "bubble") {
return false;
}
if (respondToAllBubbleMessages) {
return true;
}
const normalized = observation.message.toLowerCase();
return names
.filter((name) => name.trim().length > 0)
.some((name) => normalized.includes(name.toLowerCase()) || normalized.includes(`@${name.toLowerCase()}`));
}
function normalizeProviderAction(value, options) {
if (!isRecord(value) || typeof value.type !== "string") {
return undefined;
}
const maxTextLength = options.maxTextLength ?? DEFAULT_MAX_TEXT_LENGTH;
switch (value.type) {
case "say": {
if (typeof value.text !== "string" || value.text.trim().length === 0) {
return undefined;
}
const channel = value.channel === "local" || value.channel === "bubble" ? value.channel : options.defaultChannel ?? "bubble";
const action = {
type: "say",
channel,
text: truncateText(value.text, maxTextLength),
};
if (typeof value.author === "string") {
action.author = truncateText(value.author, 100);
}
return action;
}
case "startTyping": {
const channel = value.channel === "local" || value.channel === "bubble" ? value.channel : options.defaultChannel ?? "bubble";
const action = {
type: "startTyping",
channel,
};
if (typeof value.author === "string") {
action.author = truncateText(value.author, 100);
}
return action;
}
case "stopTyping": {
const channel = value.channel === "local" || value.channel === "bubble" ? value.channel : options.defaultChannel ?? "bubble";
const action = {
type: "stopTyping",
channel,
};
if (typeof value.author === "string") {
action.author = truncateText(value.author, 100);
}
return action;
}
case "moveTo": {
if (!isFiniteNumber(value.x) || !isFiniteNumber(value.y)) {
return undefined;
}
const action = {
type: "moveTo",
x: value.x,
y: value.y,
};
if (isFiniteNumber(value.speed) && value.speed > 0) {
action.speed = value.speed;
}
return action;
}
case "teleport": {
if (!isFiniteNumber(value.x) || !isFiniteNumber(value.y)) {
return undefined;
}
return { type: "teleport", x: value.x, y: value.y };
}
case "setStatus": {
if (value.status !== "ONLINE" &&
value.status !== "BUSY" &&
value.status !== "DO_NOT_DISTURB" &&
value.status !== "BACK_IN_A_MOMENT") {
return undefined;
}
return { type: "setStatus", status: value.status };
}
case "emitEvent": {
if (typeof value.name !== "string" || value.name.trim().length === 0) {
return undefined;
}
return {
type: "emitEvent",
name: truncateText(value.name, 200),
data: toJsonValue(value.data),
};
}
case "speakPcm": {
if (!isFiniteNumber(value.sampleRate) || typeof value.pcm16Base64 !== "string" || value.pcm16Base64.length === 0) {
return undefined;
}
return {
type: "speakPcm",
sampleRate: value.sampleRate,
pcm16Base64: value.pcm16Base64,
};
}
case "closeAudioStream": {
return { type: "closeAudioStream" };
}
case "interruptAudioStream": {
return { type: "interruptAudioStream" };
}
default:
return undefined;
}
}
function extractResponseText(value) {
if (typeof value === "string") {
return value.trim() || undefined;
}
if (!isRecord(value)) {
return undefined;
}
for (const key of ["text", "reply", "message", "content", "response", "output", "result"]) {
const entry = value[key];
if (typeof entry === "string" && entry.trim().length > 0) {
return entry.trim();
}
const nested = extractResponseText(entry);
if (nested) {
return nested;
}
}
return undefined;
}
function jsonCandidatesFromText(text) {
const trimmed = text.trim();
const candidates = [trimmed];
for (const match of trimmed.matchAll(/```(?:json)?\s*([\s\S]*?)```/gi)) {
if (match[1]) {
candidates.push(match[1].trim());
}
}
const objectStart = trimmed.indexOf("{");
const objectEnd = trimmed.lastIndexOf("}");
if (objectStart >= 0 && objectEnd > objectStart) {
candidates.push(trimmed.slice(objectStart, objectEnd + 1));
}
const arrayStart = trimmed.indexOf("[");
const arrayEnd = trimmed.lastIndexOf("]");
if (arrayStart >= 0 && arrayEnd > arrayStart) {
candidates.push(trimmed.slice(arrayStart, arrayEnd + 1));
}
return Array.from(new Set(candidates));
}
function truncateText(value, maxLength) {
if (value.length <= maxLength) {
return value;
}
return `${value.slice(0, Math.max(0, maxLength - 3))}...`;
}
function toJsonValue(value) {
if (value === undefined) {
return null;
}
return JSON.parse(JSON.stringify(value));
}
function isRecord(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isFiniteNumber(value) {
return typeof value === "number" && Number.isFinite(value);
}
//# sourceMappingURL=agentProvider.js.map