From b389c5991dde89bd637abed11942a67f1f018b17 Mon Sep 17 00:00:00 2001 From: Felix Isaac Lim <38658663+FelixIsaac@users.noreply.github.com> Date: Sat, 23 May 2026 06:18:39 +0800 Subject: [PATCH] fix: use Obsidian requestUrl instead of fetch (CSP bypass), add model dropdown native fetch blocked by Obsidian CSP for external domains; requestUrl bypasses it. Also adds Codex model dropdown with known models + custom fallback, updates normalizeModel for gpt-5.5/5.4-mini/5.3-codex-spark. Co-Authored-By: Claude Sonnet 4.6 --- src/codex-auth.ts | 22 +++++++++------- src/codex-client.ts | 18 ++++++++----- src/settings.ts | 64 ++++++++++++++++++++++++++++++++++++++------- 3 files changed, 79 insertions(+), 25 deletions(-) diff --git a/src/codex-auth.ts b/src/codex-auth.ts index be18945..eebf05c 100644 --- a/src/codex-auth.ts +++ b/src/codex-auth.ts @@ -1,5 +1,5 @@ import * as http from "http"; -import { Notice } from "obsidian"; +import { Notice, requestUrl } from "obsidian"; const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann"; const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize"; @@ -58,7 +58,8 @@ function extractAccountId(accessToken: string): string | null { } async function exchangeCode(code: string, verifier: string): Promise { - const res = await fetch(TOKEN_URL, { + const res = await requestUrl({ + url: TOKEN_URL, method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ @@ -67,12 +68,13 @@ async function exchangeCode(code: string, verifier: string): Promise= 300) return null; - const json = await res.json() as any; + const json = res.json as any; if (!json.access_token || !json.refresh_token) return null; const accountId = extractAccountId(json.access_token); @@ -87,19 +89,21 @@ async function exchangeCode(code: string, verifier: string): Promise { - const res = await fetch(TOKEN_URL, { + const res = await requestUrl({ + url: TOKEN_URL, method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: tokens.refresh, client_id: CLIENT_ID, - }), + }).toString(), + throw: false, }); - if (!res.ok) return null; + if (res.status < 200 || res.status >= 300) return null; - const json = await res.json() as any; + const json = res.json as any; if (!json.access_token || !json.refresh_token) return null; return { diff --git a/src/codex-client.ts b/src/codex-client.ts index 94e830e..b6a4f99 100644 --- a/src/codex-client.ts +++ b/src/codex-client.ts @@ -1,3 +1,5 @@ +import { requestUrl } from "obsidian"; + const CODEX_API_URL = "https://chatgpt.com/backend-api/codex/responses"; interface ResponsesInput { @@ -19,13 +21,16 @@ interface ResponsesBody { function normalizeModel(model: string): string { const m = model.toLowerCase().trim(); + if (m === "gpt-5.5" || m.includes("gpt-5.5")) return "gpt-5.5"; + if (m === "gpt-5.4-mini" || m.includes("gpt-5.4-mini")) return "gpt-5.4-mini"; + if (m.includes("gpt-5.3-codex-spark") || m.includes("codex-spark")) return "gpt-5.3-codex-spark"; if (m.includes("gpt-5.2-codex") || m.includes("gpt 5.2 codex")) return "gpt-5.2-codex"; if (m.includes("gpt-5.1-codex-max") || m.includes("codex-max")) return "gpt-5.1-codex-max"; if (m.includes("codex-mini-latest") || m.includes("codex-mini")) return "codex-mini-latest"; if (m.includes("gpt-5.1-codex") || m.includes("codex")) return "gpt-5.1-codex"; if (m.includes("gpt-5.2")) return "gpt-5.2"; if (m.includes("gpt-5.1")) return "gpt-5.1"; - return "gpt-5.1-codex"; + return m; // pass through unknown models as-is } function parseSseText(sseBody: string): string { @@ -102,7 +107,8 @@ export async function callCodexApi( include: ["reasoning.encrypted_content"], }; - const res = await fetch(CODEX_API_URL, { + const res = await requestUrl({ + url: CODEX_API_URL, method: "POST", headers: { "Content-Type": "application/json", @@ -113,14 +119,14 @@ export async function callCodexApi( "accept": "text/event-stream", }, body: JSON.stringify(body), + throw: false, }); - if (!res.ok) { - const text = await res.text().catch(() => ""); - throw new Error(`Codex API ${res.status}: ${text.slice(0, 200)}`); + if (res.status < 200 || res.status >= 300) { + throw new Error(`Codex API ${res.status}: ${res.text.slice(0, 200)}`); } - const rawText = await res.text(); + const rawText = res.text; const result = parseSseText(rawText); if (!result) throw new Error("Codex returned empty response"); return result; diff --git a/src/settings.ts b/src/settings.ts index 06f7beb..956dd8e 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -128,17 +128,61 @@ export class InlineAISettingsTab extends PluginSettingTab { } // Model setting - new Setting(containerEl) - .setName("Model") - .setDesc("Specify the model to use.") - .addText((text) => { - text.setPlaceholder("e.g., gpt-4o-mini") - .setValue(this.plugin.settings.model) - .inputEl.addEventListener("blur", async () => { - this.plugin.settings.model = text.getValue(); - await this.saveSettings(); + if (this.plugin.settings.provider === "codex") { + const CODEX_MODELS = [ + { value: "gpt-5.5", label: "GPT-5.5 (recommended)" }, + { value: "gpt-5.4-mini", label: "GPT-5.4 mini (faster)" }, + { value: "gpt-5.3-codex-spark", label: "GPT-5.3 Codex Spark (Pro only)" }, + { value: "gpt-5.2-codex", label: "GPT-5.2 Codex" }, + { value: "gpt-5.1-codex", label: "GPT-5.1 Codex" }, + { value: "gpt-5.1-codex-max", label: "GPT-5.1 Codex Max" }, + { value: "codex-mini-latest", label: "Codex Mini" }, + { value: "custom", label: "Custom…" }, + ]; + const isCustom = !CODEX_MODELS.some( + (m) => m.value === this.plugin.settings.model && m.value !== "custom", + ); + const dropdownValue = isCustom ? "custom" : this.plugin.settings.model; + + new Setting(containerEl) + .setName("Model") + .setDesc("Select a Codex model.") + .addDropdown((dd) => { + CODEX_MODELS.forEach((m) => dd.addOption(m.value, m.label)); + dd.setValue(dropdownValue).onChange(async (value) => { + if (value !== "custom") { + this.plugin.settings.model = value; + await this.saveSettings(); + } + this.display(); }); - }); + }); + + if (isCustom || dropdownValue === "custom") { + new Setting(containerEl) + .setName("Custom model ID") + .addText((text) => { + text.setPlaceholder("e.g., gpt-5.1-codex") + .setValue(isCustom ? this.plugin.settings.model : "") + .inputEl.addEventListener("blur", async () => { + this.plugin.settings.model = text.getValue(); + await this.saveSettings(); + }); + }); + } + } else { + new Setting(containerEl) + .setName("Model") + .setDesc("Specify the model to use.") + .addText((text) => { + text.setPlaceholder("e.g., gpt-4o-mini") + .setValue(this.plugin.settings.model) + .inputEl.addEventListener("blur", async () => { + this.plugin.settings.model = text.getValue(); + await this.saveSettings(); + }); + }); + } // API Key setting (conditionally displayed for OpenAI-supported endpoints) if (