CoolFace
Apppublic

LiquidAI/LFM2-MCP

sourceHugging Faceapache-2.0updated 3mo agoView on Hugging Face
39likes
utils.ts371 linesDownload Raw Back to src
1interface ParsedCall {2  name: string;3  positionalArgs: any[];4  keywordArgs: Record<string, any>;5}6 7interface Schema {8  name: string;9  description: string;10  parameters: {11    type: string;12    properties: Record<13      string,14      {15        type: string;16        description: string;17        default?: any;18      }19    >;20    required: string[];21  };22}23 24interface JSDocParam {25  type: string;26  description: string;27  isOptional: boolean;28  defaultValue?: string;29}30 31const parseArguments = (argsString: string): string[] => {32  const args: string[] = [];33  let current = "";34  let inQuotes = false;35  let quoteChar = "";36  let depth = 0;37 38  for (let i = 0; i < argsString.length; i++) {39    const char = argsString[i];40 41    if (!inQuotes && (char === '"' || char === "'")) {42      inQuotes = true;43      quoteChar = char;44      current += char;45    } else if (inQuotes && char === quoteChar) {46      inQuotes = false;47      quoteChar = "";48      current += char;49    } else if (!inQuotes && char === "(") {50      depth++;51      current += char;52    } else if (!inQuotes && char === ")") {53      depth--;54      current += char;55    } else if (!inQuotes && char === "," && depth === 0) {56      args.push(current.trim());57      current = "";58    } else {59      current += char;60    }61  }62 63  if (current.trim()) {64    args.push(current.trim());65  }66 67  return args;68};69 70export const extractPythonicCalls = (toolCallContent: string): string[] => {71  try {72    const cleanContent = toolCallContent.trim();73 74    try {75      const parsed = JSON.parse(cleanContent);76      if (Array.isArray(parsed)) {77        return parsed;78      }79    } catch {80      // Fallback to manual parsing81    }82 83    if (cleanContent.startsWith("[") && cleanContent.endsWith("]")) {84      const inner = cleanContent.slice(1, -1).trim();85      if (!inner) return [];86      return parseArguments(inner).map((call) =>87        call.trim().replace(/^['"]|['"]$/g, ""),88      );89    }90 91    return [cleanContent];92  } catch (error) {93    console.error("Error parsing tool calls:", error);94    return [];95  }96};97 98export const parsePythonicCalls = (command: string): ParsedCall | null => {99  const callMatch = command.match(/^([a-zA-Z0-9_]+)\((.*)\)$/);100  if (!callMatch) return null;101 102  const [, name, argsStr] = callMatch;103  const args = parseArguments(argsStr);104  const positionalArgs: any[] = [];105  const keywordArgs: Record<string, any> = {};106 107  for (const arg of args) {108    const kwargMatch = arg.match(/^([a-zA-Z0-9_]+)\s*=\s*(.*)$/);109    if (kwargMatch) {110      const [, key, value] = kwargMatch;111      try {112        keywordArgs[key] = JSON.parse(value);113      } catch {114        keywordArgs[key] = value;115      }116    } else {117      try {118        positionalArgs.push(JSON.parse(arg));119      } catch {120        positionalArgs.push(arg);121      }122    }123  }124  return { name, positionalArgs, keywordArgs };125};126 127export const extractFunctionAndRenderer = (128  code: string,129): { functionCode: string; rendererCode?: string } => {130  if (typeof code !== "string") {131    return { functionCode: code };132  }133 134  const exportMatch = code.match(/export\s+default\s+/);135  if (!exportMatch) {136    return { functionCode: code };137  }138 139  const exportIndex = exportMatch.index!;140  const functionCode = code.substring(0, exportIndex).trim();141  const rendererCode = code.substring(exportIndex).trim();142 143  return { functionCode, rendererCode };144};145 146/**147 * Helper function to extract JSDoc parameters from JSDoc comments.148 */149const extractJSDocParams = (150  jsdoc: string,151): Record<string, JSDocParam & { jsdocDefault?: string }> => {152  const jsdocParams: Record<string, JSDocParam & { jsdocDefault?: string }> =153    {};154  const lines = jsdoc155    .split("\n")156    .map((line) => line.trim().replace(/^\*\s?/, ""));157  const paramRegex =158    /@param\s+\{([^}]+)\}\s+(\[?[a-zA-Z0-9_]+(?:=[^\]]+)?\]?|\S+)\s*-?\s*(.*)?/;159 160  for (const line of lines) {161    const paramMatch = line.match(paramRegex);162    if (paramMatch) {163      let [, type, namePart, description] = paramMatch;164      description = description || "";165      let isOptional = false;166      let name = namePart;167      let jsdocDefault: string | undefined = undefined;168 169      if (name.startsWith("[") && name.endsWith("]")) {170        isOptional = true;171        name = name.slice(1, -1);172      }173      if (name.includes("=")) {174        const [n, def] = name.split("=");175        name = n.trim();176        jsdocDefault = def.trim().replace(/['"]/g, "");177      }178 179      jsdocParams[name] = {180        type: type.toLowerCase(),181        description: description.trim(),182        isOptional,183        defaultValue: undefined,184        jsdocDefault,185      };186    }187  }188  return jsdocParams;189};190 191/**192 * Helper function to extract function signature information.193 */194const extractFunctionSignature = (195  functionCode: string,196): {197  name: string;198  params: { name: string; defaultValue?: string }[];199} | null => {200  const functionSignatureMatch = functionCode.match(201    /function\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)/,202  );203  if (!functionSignatureMatch) {204    return null;205  }206 207  const functionName = functionSignatureMatch[1];208  const params = functionSignatureMatch[2]209    .split(",")210    .map((p) => p.trim())211    .filter(Boolean)212    .map((p) => {213      const [name, defaultValue] = p.split("=").map((s) => s.trim());214      return { name, defaultValue };215    });216 217  return { name: functionName, params };218};219 220export const generateSchemaFromCode = (code: string): Schema => {221  const { functionCode } = extractFunctionAndRenderer(code);222 223  if (typeof functionCode !== "string") {224    return {225      name: "invalid_code",226      description: "Code is not a valid string.",227      parameters: { type: "object", properties: {}, required: [] },228    };229  }230 231  // 1. Extract function signature, name, and parameter names directly from the code232  const signatureInfo = extractFunctionSignature(functionCode);233  if (!signatureInfo) {234    return {235      name: "invalid_function",236      description: "Could not parse function signature.",237      parameters: { type: "object", properties: {}, required: [] },238    };239  }240 241  const { name: functionName, params: paramsFromSignature } = signatureInfo;242 243  const schema: Schema = {244    name: functionName,245    description: "",246    parameters: {247      type: "object",248      properties: {},249      required: [],250    },251  };252 253  // 2. Parse JSDoc comments to get descriptions and types254  const jsdocMatch = functionCode.match(/\/\*\*([\s\S]*?)\*\//);255  let jsdocParams: Record<string, JSDocParam & { jsdocDefault?: string }> = {};256  if (jsdocMatch) {257    const jsdoc = jsdocMatch[1];258    jsdocParams = extractJSDocParams(jsdoc);259 260    const descriptionLines = jsdoc261      .split("\n")262      .map((line) => line.trim().replace(/^\*\s?/, ""))263      .filter((line) => !line.startsWith("@") && line);264 265    schema.description = descriptionLines.join(" ").trim();266  }267 268  // 3. Combine signature parameters with JSDoc info269  for (const param of paramsFromSignature) {270    const paramName = param.name;271    const jsdocInfo = jsdocParams[paramName];272    schema.parameters.properties[paramName] = {273      type: jsdocInfo ? jsdocInfo.type : "any",274      description: jsdocInfo ? jsdocInfo.description : "",275    };276 277    // Prefer default from signature, then from JSDoc278    if (param.defaultValue !== undefined) {279      // Try to parse as JSON, fallback to string280      try {281        schema.parameters.properties[paramName].default = JSON.parse(282          param.defaultValue.replace(/'/g, '"'),283        );284      } catch {285        schema.parameters.properties[paramName].default = param.defaultValue;286      }287    } else if (jsdocInfo && jsdocInfo.jsdocDefault !== undefined) {288      schema.parameters.properties[paramName].default = jsdocInfo.jsdocDefault;289    }290 291    // A parameter is required if:292    // - Not optional in JSDoc293    // - No default in signature294    // - No default in JSDoc295    const hasDefault =296      param.defaultValue !== undefined ||297      (jsdocInfo && jsdocInfo.jsdocDefault !== undefined);298    if (!jsdocInfo || (!jsdocInfo.isOptional && !hasDefault)) {299      schema.parameters.required.push(paramName);300    }301  }302 303  return schema;304};305 306/**307 * Extracts tool call content from a string using the tool call markers.308 */309export const extractToolCallContent = (content: string): string | null => {310  const toolCallMatch = content.match(311    /<\|tool_call_start\|>(.*?)<\|tool_call_end\|>/s,312  );313  return toolCallMatch ? toolCallMatch[1].trim() : null;314};315 316/**317 * Maps positional and keyword arguments to named parameters based on schema.318 */319export const mapArgsToNamedParams = (320  paramNames: string[],321  positionalArgs: any[],322  keywordArgs: Record<string, any>,323): Record<string, any> => {324  const namedParams: Record<string, any> = Object.create(null);325  positionalArgs.forEach((arg, idx) => {326    if (idx < paramNames.length) {327      namedParams[paramNames[idx]] = arg;328    }329  });330  Object.assign(namedParams, keywordArgs);331  return namedParams;332};333 334export const getErrorMessage = (error: unknown): string => {335  if (error instanceof Error) {336    return error.message;337  }338  if (typeof error === "string") {339    return error;340  }341  if (error && typeof error === "object") {342    return JSON.stringify(error);343  }344  return String(error);345};346 347/**348 * Adapted from https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser.349 */350export function isMobileOrTablet() {351  let check = false;352  (function (a: string) {353    if (354      /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(355        a,356      ) ||357      /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(358        a.slice(0, 4),359      )360    )361      check = true;362  })(363    navigator.userAgent ||364      navigator.vendor ||365      ("opera" in window && typeof window.opera === "string"366        ? window.opera367        : ""),368  );369  return check;370}371