feat(ai): add OpenAI-compatible provider (custom base URL)#97
Open
Mariomarquezt wants to merge 7 commits into
Open
feat(ai): add OpenAI-compatible provider (custom base URL)#97Mariomarquezt wants to merge 7 commits into
Mariomarquezt wants to merge 7 commits into
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…olish - Add normalizeOpenAiBaseUrl() to chatCompletions.ts that strips trailing slashes and an optional trailing /v1 segment, preventing the /v1/v1/ double-append footgun when users paste provider-documented URLs. - Use normalizeOpenAiBaseUrl in makeChatCompletionsAdapter (endpoint) and fetchOpenAiCompatibleModels (/v1/models fetch); drop the now-unused trimSlash import from openaiCompatible.ts. - Remove redundant 'as AiProviderId' cast (M4); drop the unused import. - Add normalizeOpenAiBaseUrl test coverage in chatCompletions.test.ts and a /v1-suffixed base-URL normalization case in openaiCompatible.test.ts. - Update AiAuthMode baseUrl JSDoc to reflect Ollama + openai-compatible (M1). - Add OpenAI-Compatible to contextTokens.ts comment for parity (M3). - Update ProvidersTab base-URL placeholder to https://api.groq.com/openai/v1 so the UI matches the now-correct /v1-inclusive provider-documented form. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0115W5vEDNwsWeaeS5PyTFgG
The id stays 'openai-compatible' (stable registry/DB identifier); only the user-facing display label changes — dropdown, credential card, driver label, and docs. Protocol descriptions and the filename are unchanged.
Real OpenAI-compatible gateways (OpenCode Zen, OpenRouter, vLLM, …) send explicit `null` for optional per-chunk fields (`usage: null`, `tool_calls: null`, `delta.content: null`) on every chunk. The chunk schema used Type.Optional, which accepts absent-or-value but not null, so parseValue threw, the frame was dropped in translate()'s catch, and the model's entire reply silently vanished — reasoning models (GLM, DeepSeek, Qwen, MiniMax) appeared to 'not reply'. Wrap the optional fields in a nullable() helper so both absent and null validate. Verified against real gateway frames.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Instatic's AI provider list is closed (Anthropic / OpenAI / OpenRouter / Ollama). This adds a generic OpenAI-Compatible provider so the editor agent can target any endpoint that speaks the OpenAI
/v1/chat/completionswire protocol — Groq, Together, DeepSeek, Fireworks, MiniMax, self-hosted vLLM / LM Studio, and aggregator gateways — via a custom base URL + optional API key.Approach
ollama.tsinto a sharedserver/ai/drivers/http/chatCompletions.ts; bothollamaand the new driver consume it.server/ai/drivers/openaiCompatible.ts:baseUrlauth (optional bearer), model discovery viaGET {baseUrl}/v1/models, generic capability defaults.AiProviderId,DRIVERS, credentials + models handlers) and the admin UI (ProvidersTab,api.ts)./v1(so both…/openaiand…/openai/v1work); the SSE chunk schema tolerates explicitnullforusage/tool_calls/content, which many gateways send on every chunk — without this,parseValuethrows and the model's entire reply is silently dropped.provider_idenum constraint was already dropped; validation is at the app boundary).User / developer impact
Operators can add any OpenAI-compatible endpoint under Settings → AI → Providers → "OpenAI-Compatible", enter a base URL + key, and use it like any built-in provider. The shared adapter also hardens Ollama against the same
null-chunk variance.Verification
bun test— new driver + shared-adapter unit tests (model-list parse, failure→[], SSE translation,null-tolerance), plus updated architecture gate.bun run build(tsc -b && vite build) andbun run lint— green.Disclosure
Authored with Claude Code (Claude Opus 4.8), reviewed and live-tested by the submitter. Harness: Claude Code.