mirror of
https://github.com/fbarrca/obsidian-inlineAI.git
synced 2026-07-22 11:50:24 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
parent
6880043c8b
commit
b389c5991d
3 changed files with 79 additions and 25 deletions
|
|
@ -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<CodexTokens | null> {
|
||||
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<CodexTokens
|
|||
code,
|
||||
code_verifier: verifier,
|
||||
redirect_uri: REDIRECT_URI,
|
||||
}),
|
||||
}).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;
|
||||
|
||||
const accountId = extractAccountId(json.access_token);
|
||||
|
|
@ -87,19 +89,21 @@ async function exchangeCode(code: string, verifier: string): Promise<CodexTokens
|
|||
}
|
||||
|
||||
export async function refreshCodexToken(tokens: CodexTokens): Promise<CodexTokens | null> {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
Loading…
Reference in a new issue