// Pure URL construction. No browser APIs, so node:test imports this directly. // // Every ChatGPT parameter below is undocumented by OpenAI and has broken before. // This module's contract is only that it emits a well-formed URL; whether // ChatGPT honours temporary-chat / hints is out of our control, which is // why each is independently omittable and every label is hint-strength. // // The root path is mandatory: chatgpt.com/search began returning 404 in // mid-2025, and the root form is what OpenAI's own extension uses. const BASE = "https://chatgpt.com/"; export const MAX_URL_CHARS = 8000; export function applyTemplate(template, query) { if (query === "") { return ""; } const tpl = typeof template === "string" ? template : ""; if (tpl.includes("{query}")) { // Replacer function, not a replacement string: String.prototype.replaceAll // gives $&, $`, $', and $$ special meaning in a replacement STRING, and // `query` is arbitrary page-selected text that can contain any of those // (shell one-liners routinely do). The function form disables that // interpretation entirely. Do not "simplify" this back to the string form. return tpl.replaceAll("{query}", () => query); } const trimmed = tpl.trim(); return trimmed === "" ? query : `${trimmed} ${query}`; } function addOptionalParams(url, settings) { if (settings.temporaryChat) { url.searchParams.set("temporary-chat", "true"); } if (settings.webSearch) { url.searchParams.set("hints", "search"); } return url; } // Encode a complete prompt without silently shortening it; prepareAction owns // the length budget and asks before shortening. q goes first, matching the // most-cited working examples. export function serializePrompt(settings, prompt) { const url = new URL(BASE); if (prompt) url.searchParams.set("q", prompt); return addOptionalParams(url, settings).href; }