const INDENT_STEP = " "; // 4 spaces for indentation function stringifyJsJsonRecursive(value: unknown, currentIndent: string): string { if (typeof value === "string") return `"${value}"`; if (value === null) return "null"; if (typeof value === "boolean" || typeof value === "number") return String(value); if (typeof value === "object" && value !== null) { const nextIndent = currentIndent + INDENT_STEP; if (Array.isArray(value)) { if (value.length === 0) return "[]"; const items = value.map(v => stringifyJsJsonRecursive(v, nextIndent)); return "[\n" + items.map(item => nextIndent + item).join(",\n") + "\n" + currentIndent + "]"; } const entries = Object.entries(value); if (entries.length === 0) return "{}"; const properties = entries.map(([k, v]) => `${nextIndent}"${k}": ${stringifyJsJsonRecursive(v, nextIndent)}`); return "{\n" + properties.join(",\n") + "\n" + currentIndent + "}"; } return String(value); // Fallback for other types } function formatJsJsonValue(value: unknown, baseIndent: string): string { return stringifyJsJsonRecursive(value, baseIndent); } function stringifyPythonRecursive(value: unknown, currentIndent: string): string { if (typeof value === "string") return `"${value}"`; if (typeof value === "boolean") return value ? "True" : "False"; if (value === null) return "None"; if (typeof value === "number") return String(value); if (typeof value === "object" && value !== null) { const nextIndent = currentIndent + INDENT_STEP; if (Array.isArray(value)) { if (value.length === 0) return "[]"; const items = value.map(v => stringifyPythonRecursive(v, nextIndent)); return "[\n" + items.map(item => nextIndent + item).join(",\n") + "\n" + currentIndent + "]"; } const entries = Object.entries(value); if (entries.length === 0) return "{}"; // In Python, dictionary keys are typically strings. const properties = entries.map(([k, v]) => `${nextIndent}"${k}": ${stringifyPythonRecursive(v, nextIndent)}`); return "{\n" + properties.join(",\n") + "\n" + currentIndent + "}"; } return String(value); // Fallback } function formatPythonValue(value: unknown, baseIndent: string): string { return stringifyPythonRecursive(value, baseIndent); } /** * Inserts new properties into a code snippet block (like a JS object or Python dict). */ function insertPropertiesInternal( snippet: string, newProperties: Record, blockStartMarker: RegExp, // Regex to find the character *opening* the block (e.g., '{' or '(') openChar: string, // The opening character, e.g., '{' or '(' closeChar: string, // The closing character, e.g., '}' or ')' propFormatter: (key: string, formattedValue: string, indent: string) => string, valueFormatter: (value: unknown, baseIndent: string) => string ): string { if (Object.keys(newProperties).length === 0) { return snippet; } const match = snippet.match(blockStartMarker); // match.index is the start of the whole marker, e.g. "client.chatCompletionStream(" // We need the index of the openChar itself. if (!match || typeof match.index !== "number") { return snippet; } const openCharIndex = snippet.indexOf(openChar, match.index + match[0].length - 1); if (openCharIndex === -1) { return snippet; } let balance = 1; let closeCharIndex = -1; for (let i = openCharIndex + 1; i < snippet.length; i++) { if (snippet[i] === openChar) { balance++; } else if (snippet[i] === closeChar) { balance--; } if (balance === 0) { closeCharIndex = i; break; } } if (closeCharIndex === -1) { return snippet; // Malformed or not found } const contentBeforeBlock = snippet.substring(0, openCharIndex + 1); const currentContent = snippet.substring(openCharIndex + 1, closeCharIndex); const contentAfterBlock = snippet.substring(closeCharIndex); // Determine indentation let indent = ""; const lines = currentContent.split("\n"); if (lines.length > 1) { for (const line of lines) { const lineIndentMatch = line.match(/^(\s+)\S/); if (lineIndentMatch) { indent = lineIndentMatch[1] ?? ""; break; } } } if (!indent) { // If no indent found, or content is empty/single line, derive from openChar line const lineOfOpenCharStart = snippet.lastIndexOf("\n", openCharIndex) + 1; const lineOfOpenChar = snippet.substring(lineOfOpenCharStart, openCharIndex); const openCharLineIndentMatch = lineOfOpenChar.match(/^(\s*)/); indent = (openCharLineIndentMatch ? openCharLineIndentMatch[1] : "") + " "; // Default to 4 spaces more } let newPropsStr = ""; Object.entries(newProperties).forEach(([key, value]) => { newPropsStr += propFormatter(key, valueFormatter(value, indent), indent); }); const trimmedOriginalContent = currentContent.trim(); let combinedContent; if (trimmedOriginalContent) { // There was actual non-whitespace content. // Preserve original currentContent structure as much as possible. // Find the end of the textual part of currentContent (before any pure trailing whitespace). let endOfTextualPart = currentContent.length; while (endOfTextualPart > 0 && /\s/.test(currentContent.charAt(endOfTextualPart - 1))) { endOfTextualPart--; } const textualPartOfCurrentContent = currentContent.substring(0, endOfTextualPart); const trailingWhitespaceOfCurrentContent = currentContent.substring(endOfTextualPart); let processedTextualPart = textualPartOfCurrentContent; if (processedTextualPart && !processedTextualPart.endsWith(",")) { processedTextualPart += ","; } // Add a newline separator if the original trailing whitespace doesn't end with one. const separator = trailingWhitespaceOfCurrentContent.endsWith("\n") || trailingWhitespaceOfCurrentContent.endsWith("\r") ? "" : "\n"; combinedContent = processedTextualPart + trailingWhitespaceOfCurrentContent + separator + newPropsStr; } else { // currentContent was empty or contained only whitespace. // Check if the original block opening (e.g., '{' or '(') was immediately followed by a newline. const openCharFollowedByNewline = snippet[openCharIndex + 1] === "\n" || (snippet[openCharIndex + 1] === "\r" && snippet[openCharIndex + 2] === "\n"); if (openCharFollowedByNewline) { combinedContent = newPropsStr; // newPropsStr already starts with indent } else { combinedContent = "\n" + newPropsStr; // Add a newline first, then newPropsStr } } // Remove the trailing comma (and its trailing whitespace/newline) from the last property added. combinedContent = combinedContent.replace(/,\s*$/, ""); // Ensure the block content ends with a newline, and the closing character is on its own line, indented. if (combinedContent.trim()) { // If there's any actual content in the block if (!combinedContent.endsWith("\n")) { combinedContent += "\n"; } // Determine the base indent for the closing character's line const lineOfOpenCharStart = snippet.lastIndexOf("\n", openCharIndex) + 1; const openCharLine = snippet.substring(lineOfOpenCharStart, openCharIndex); const baseIndentMatch = openCharLine.match(/^(\s*)/); const baseIndent = baseIndentMatch ? baseIndentMatch[1] : ""; combinedContent += baseIndent; } else { // Block is effectively empty (e.g., was {} and no properties added, or newPropsStr was empty - though current logic prevents this if newProperties is not empty). // Format as an empty block with the closing char on a new, indented line. const lineOfOpenCharStart = snippet.lastIndexOf("\n", openCharIndex) + 1; const openCharLine = snippet.substring(lineOfOpenCharStart, openCharIndex); const baseIndentMatch = openCharLine.match(/^(\s*)/); const baseIndent = baseIndentMatch ? baseIndentMatch[1] : ""; const openCharFollowedByNewline = snippet[openCharIndex + 1] === "\n" || (snippet[openCharIndex + 1] === "\r" && snippet[openCharIndex + 2] === "\n"); if (openCharFollowedByNewline) { // Original was like {\n} combinedContent = baseIndent; // Just the indent for the closing char } else { // Original was like {} combinedContent = "\n" + baseIndent; // Newline, then indent for closing char } } return contentBeforeBlock + combinedContent + contentAfterBlock; } export function modifySnippet(snippet: string, newProperties: Record): string { // JS: HuggingFace InferenceClient (streaming) if (snippet.includes("client.chatCompletionStream")) { return insertPropertiesInternal( snippet, newProperties, /client\.chatCompletionStream\s*\(\s*/, // Finds "client.chatCompletionStream(" "{", // The parameters are in an object literal "}", (key, value, indent) => `${indent}${key}: ${value},\n`, // JS object literal style formatJsJsonValue ); } // JS: HuggingFace InferenceClient (non-streaming) else if (snippet.includes("client.chatCompletion") && snippet.includes("InferenceClient")) { // Ensure it's not the OpenAI client by also checking for InferenceClient return insertPropertiesInternal( snippet, newProperties, /client\.chatCompletion\s*\(\s*/, // Finds "client.chatCompletion(" "{", // The parameters are in an object literal "}", (key, value, indent) => `${indent}${key}: ${value},\n`, // JS object literal style formatJsJsonValue ); } // JS: OpenAI Client // Check for client.chat.completions.create and a common JS import pattern else if ( snippet.includes("client.chat.completions.create") && (snippet.includes('import { OpenAI } from "openai"') || snippet.includes("new OpenAI(")) ) { return insertPropertiesInternal( snippet, newProperties, /client\.chat\.completions\.create\s*\(\s*/, // Finds "client.chat.completions.create(" "{", // The parameters are in an object literal "}", (key, value, indent) => `${indent}${key}: ${value},\n`, formatJsJsonValue ); } // Python: OpenAI or HuggingFace Client using client.chat.completions.create else if (snippet.includes("client.chat.completions.create")) { return insertPropertiesInternal( snippet, newProperties, /client\.chat\.completions\.create\s*\(/, // Finds "client.chat.completions.create(" "(", // The parameters are directly in the function call tuple ")", (key, value, indent) => { const snakeKey = key.replace(/([A-Z])/g, "_$1").toLowerCase(); return `${indent}${snakeKey}=${value},\n`; }, formatPythonValue ); } // Python: requests example with query({...}) else if (snippet.includes("def query(payload):") && snippet.includes("query({")) { return insertPropertiesInternal( snippet, newProperties, /query\s*\(\s*/, // Finds "query(" and expects a dictionary literal next "{", // The parameters are in a dictionary literal "}", // Python dict keys are strings, values formatted for Python (key, formattedValue, indent) => `${indent}"${key}": ${formattedValue},\n`, formatPythonValue // Use formatPythonValue for the values themselves ); } // Shell/curl (JSON content) else if (snippet.includes("curl") && snippet.includes("-d")) { return insertPropertiesInternal( snippet, newProperties, /-d\s*'(?:\\n)?\s*/, "{", "}", (key, value, indent) => { const snakeKey = key.replace(/([A-Z])/g, "_$1").toLowerCase(); return `${indent}"${snakeKey}": ${value},\n`; }, formatJsJsonValue ); } return snippet; }