mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-10-01 17:29:29 +05:30
feat(ai): add Anthropic-compatible provider; fix async plugin backends
Anthropic-compatible provider: - New 'anthropic-compatible' option for any base URL speaking the Anthropic messages schema (LiteLLM proxies, Bedrock gateways, local servers) - Sends x-api-key AND Bearer auth when a key is set (gateway-friendly, harmless for the official API); keyless proxies supported - Tolerates base URLs with or without a trailing /v1 segment - Settings modal, manifest, and provider docs updated Runtime bug fixes found by booting the app (run-to-verify pass): - PDF editor: File > Open PDF sends operation=null which matched no switch case and crashed on getElementById(undefined); now defaults to the merge section - backlinks-panel: wrong require depth (../../utils -> ../utils) threw at panel registration time - writing-studio stack was written against a synchronous settings backend but the real one is IPC-backed: GoalTracker/SnapshotManager/ProjectManager and all four panels now await; JSON.parse(Promise) crashes eliminated - manuscript panel used window.prompt (unavailable in Electron); replaced with an inline dialog - collaboration comment-store/save-load made async to match its IPC IO 617/617 tests green; 4 consecutive clean app boots (no uncaught errors)
This commit is contained in:
+32
-8
@@ -12,6 +12,10 @@
|
||||
* - `ollama` → http://localhost:11434/v1 (OpenAI-compatible)
|
||||
* - `lmstudio` → http://localhost:1234/v1 (OpenAI-compatible)
|
||||
* - `openai-compatible`→ any baseUrl speaking the OpenAI chat schema
|
||||
* - `anthropic-compatible` → any baseUrl speaking the Anthropic messages
|
||||
* schema (LiteLLM proxies, Bedrock gateways, local Claude-compatible
|
||||
* servers); baseUrl is required, the API key is optional because many
|
||||
* proxies are keyless or front their own auth
|
||||
*
|
||||
* The module takes an injectable `fetchImpl` (defaulting to global fetch) so
|
||||
* tests can stub the network without monkey-patching.
|
||||
@@ -33,6 +37,7 @@ const PROVIDER_DEFAULTS = {
|
||||
ollama: { baseUrl: 'http://localhost:11434/v1', defaultModel: 'llama3.1' },
|
||||
lmstudio: { baseUrl: 'http://localhost:1234/v1', defaultModel: 'local-model' },
|
||||
'openai-compatible': { baseUrl: '', defaultModel: '' },
|
||||
'anthropic-compatible': { baseUrl: '', defaultModel: 'claude-3-5-sonnet-latest' },
|
||||
};
|
||||
|
||||
/** Provider ids that speak the OpenAI chat-completions schema. */
|
||||
@@ -72,8 +77,13 @@ function resolveSettings({
|
||||
);
|
||||
}
|
||||
|
||||
// Local providers (ollama/lmstudio) don't need a key; remote ones do.
|
||||
const needsKey = requireKey && provider !== 'ollama' && provider !== 'lmstudio';
|
||||
// Local providers (ollama/lmstudio) and self-hosted *-compatible proxies
|
||||
// don't need a key; branded cloud endpoints do.
|
||||
const needsKey =
|
||||
requireKey &&
|
||||
provider !== 'ollama' &&
|
||||
provider !== 'lmstudio' &&
|
||||
provider !== 'anthropic-compatible';
|
||||
if (needsKey && !apiKey) {
|
||||
throw new AiProviderError(
|
||||
`The "${provider}" provider needs an API key. Add one in AI Assistant settings.`,
|
||||
@@ -158,13 +168,27 @@ async function callAnthropic(settings, { system, messages }, fetchImpl, timeoutM
|
||||
system: system || undefined,
|
||||
messages: messages.map((m) => ({ role: m.role, content: m.content })),
|
||||
};
|
||||
const response = await fetchImpl(`${settings.baseUrl}/v1/messages`, {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'anthropic-version': '2023-06-01',
|
||||
};
|
||||
if (settings.apiKey) {
|
||||
// Official API keys travel via x-api-key; many compatible gateways
|
||||
// (LiteLLM proxies, Bedrock fronts) expect a Bearer token instead.
|
||||
// Sending both is harmless for the official endpoint and maximizes
|
||||
// proxy compatibility.
|
||||
headers['x-api-key'] = settings.apiKey;
|
||||
headers.Authorization = `Bearer ${settings.apiKey}`;
|
||||
}
|
||||
// Bases may or may not already carry the /v1 prefix — handle both so a
|
||||
// "http://host:4000" proxy base and an "http://host:4000/v1" style base
|
||||
// both land on a single /v1/messages path.
|
||||
const messagesUrl = settings.baseUrl.endsWith('/v1')
|
||||
? `${settings.baseUrl}/messages`
|
||||
: `${settings.baseUrl}/v1/messages`;
|
||||
const response = await fetchImpl(messagesUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': settings.apiKey,
|
||||
'anthropic-version': '2023-06-01',
|
||||
},
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user