CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
extension-mention.ts180 linesDownload Raw Back to utils
1/**2 * @license3 * Copyright 2025 Qwen4 * SPDX-License-Identifier: Apache-2.05 */6 7import * as fs from 'node:fs/promises';8import type { Extension } from '@qwen-code/qwen-code-core';9import {10  getErrorMessage,11  isSubpath,12  stripTerminalControlSequences,13} from '@qwen-code/qwen-code-core';14 15export const EXTENSION_REF_PREFIX = 'ext:';16export const EXTENSION_CONTEXT_BUDGET = 200_000;17export const EXTENSION_CONTEXT_FILE_CAP = 50_000;18 19/**20 * Parses an `ext:<name>` reference string. Returns the extension name21 * portion if the input starts with the extension prefix, or `null` otherwise.22 */23export function parseExtensionRef(pathName: string): { name: string } | null {24  if (!pathName.startsWith(EXTENSION_REF_PREFIX)) return null;25  const name = pathName.slice(EXTENSION_REF_PREFIX.length);26  if (!name) return null;27  return { name };28}29 30export function buildExtensionRef(extensionName: string): string {31  return `${EXTENSION_REF_PREFIX}${extensionName}`;32}33 34export function matchExtensionByRef(35  name: string,36  extensions: Extension[],37): Extension | undefined {38  const lower = name.toLowerCase();39  return extensions.find(40    (ext) =>41      ext.name.toLowerCase() === lower ||42      ext.config.name.toLowerCase() === lower,43  );44}45 46const BIDI_CONTROL_RE = /[‎‏؜⁦⁧⁨⁩‪‫‬‭‮]/g;47 48export function sanitizeDisplayText(raw: string): string | null {49  const stripped = stripTerminalControlSequences(raw)50    .replace(BIDI_CONTROL_RE, '')51    .replace(/\s+/g, ' ')52    .trim();53  return stripped.length > 0 ? stripped : null;54}55 56export function getExtensionDisplayName(extension: Extension): string {57  return (58    sanitizeDisplayText(extension.displayName || extension.name) ||59    extension.name60  );61}62 63export function buildExtensionContextText(extension: Extension): string {64  const displayName = getExtensionDisplayName(extension);65  const lines: string[] = [];66 67  lines.push(68    `--- Extension: ${displayName} (untrusted third-party content) ---`,69  );70  if (extension.config.description) {71    const desc = sanitizeDisplayText(extension.config.description);72    if (desc) {73      lines.push(desc);74      lines.push('');75    }76  }77 78  const capabilities: string[] = [];79 80  if (extension.skills && extension.skills.length > 0) {81    const skillNames = extension.skills82      .map((s) => sanitizeDisplayText(s.name) || s.name)83      .join(', ');84    capabilities.push(`- Skills: ${skillNames} (invoke via /<skill-name>)`);85  }86 87  if (extension.mcpServers && Object.keys(extension.mcpServers).length > 0) {88    const serverNames = Object.keys(extension.mcpServers)89      .map((n) => sanitizeDisplayText(n) || n)90      .join(', ');91    capabilities.push(`- MCP Servers: ${serverNames}`);92  }93 94  if (extension.agents && extension.agents.length > 0) {95    const agentNames = extension.agents96      .map((a) => sanitizeDisplayText(a.name) || a.name)97      .join(', ');98    capabilities.push(`- Agents: ${agentNames}`);99  }100 101  if (capabilities.length > 0) {102    lines.push('Available capabilities from this extension:');103    lines.push(...capabilities);104    lines.push('');105  }106 107  lines.push(`--- End Extension: ${displayName} ---`);108 109  return lines.join('\n');110}111 112export async function buildExtensionMentionContext(113  extension: Extension,114  options: {115    remainingBudget: number;116    signal?: AbortSignal;117    onDebugMessage?: (message: string) => void;118  },119): Promise<{ text: string; remainingBudget: number }> {120  let contextText = buildExtensionContextText(extension);121  let remainingBudget = options.remainingBudget;122 123  if (extension.contextFiles.length === 0) {124    return { text: contextText, remainingBudget };125  }126 127  const fileReads = await Promise.allSettled(128    extension.contextFiles.map(async (contextFilePath) => {129      let realPath: string;130      let realExtPath: string;131      try {132        realPath = await fs.realpath(contextFilePath);133        realExtPath = await fs.realpath(extension.path);134      } catch {135        options.onDebugMessage?.(136          `Skipping unreadable context file: ${contextFilePath}`,137        );138        return null;139      }140      if (!isSubpath(realExtPath, realPath)) {141        options.onDebugMessage?.(142          `Skipping context file outside extension directory: ${contextFilePath}`,143        );144        return null;145      }146      return fs.readFile(realPath, {147        encoding: 'utf-8',148        signal: options.signal,149      });150    }),151  );152 153  for (let i = 0; i < fileReads.length; i++) {154    const outcome = fileReads[i];155    if (outcome.status === 'rejected') {156      options.onDebugMessage?.(157        `Failed to read extension context file ${extension.contextFiles[i]}: ${getErrorMessage(outcome.reason)}`,158      );159      continue;160    }161    const content = outcome.value;162    if (!content || !content.trim()) continue;163    if (remainingBudget <= 0) {164      options.onDebugMessage?.(165        'Extension context budget exhausted, skipping remaining files.',166      );167      break;168    }169    const cap = Math.min(EXTENSION_CONTEXT_FILE_CAP, remainingBudget);170    const cappedContent =171      content.length > cap172        ? content.slice(0, cap) + '\n... (truncated)'173        : content;174    contextText += `\n\n${cappedContent}`;175    remainingBudget -= cappedContent.length;176  }177 178  return { text: contextText, remainingBudget };179}180 
basant307/AI_Governance_Project · CoolFace