Skip to content

fix: support OpenCode & OpenRouter gateways in OpenAI-compatible provider - #36

Open
akrista wants to merge 1 commit into
AienTech:mainfrom
akrista:fix/openai-compatible-gateways
Open

akrista wants to merge 1 commit into
AienTech:mainfrom
akrista:fix/openai-compatible-gateways

Conversation

@akrista

@akrista akrista commented Oct 29, 2026

Copy link
Copy Markdown

Summary

Following the feedback on #19 (using OpenAI-compatible rather than adding a separate provider enum), we tested OpenCode (opencode.ai) and OpenRouter (openrouter.ai) through OpenAICompatibleProvider and hit four interoperability issues that prevented model listing, connection checks, and structured JSON extraction from succeeding.

This PR keeps everything inside OpenAICompatibleProvider with no database or UI changes:

  1. Gateway URL normalization & OpenCode headers (normalizeBaseURL, isOpenCode, isOpenRouter):
    • OpenCode: Normalizes /inference/go(/openai/v1)? and /inference(/openai/v1)? to their /v1 root (/inference/go/v1, /inference/v1), where both GET /models (200 OK) and POST /chat/completions are served (GET .../openai/v1/models returns 404). Attaches User-Agent: Dossier/<version> and x-opencode-session: ses_<uuid>, which OpenCode requires on /chat/completions (otherwise returning 400 MissingSessionID).
    • OpenRouter: Normalizes https://openrouter.ai and https://openrouter.ai/v1 to https://openrouter.ai/api/v1 so /v1 suffixes don't hit OpenRouter's web 404 page.
    • Public model lists without a saved key: Omits the dummy Authorization: Bearer not-needed header when apiKey is null so public model catalogs (like OpenCode's /v1/models) don't reject unauthenticated model-list requests with 401 Invalid credential.
  2. Gateway & complex-schema fallback (SCHEMA_REFUSED, JSON_REFUSED):
    • Matches complex-schema refusals (grammar is too large, parameters with union types, optional parameters, schema is too complex, as in Resume import fails with Anthropic: “compiled grammar is too large” #24 / AnthropicProvider) and remembers them per ${baseURL} ${model} ${schemaName} in promptSchemas so large schemas like extract_profile fall back to prompt instructions while simpler schemas keep structured output.
    • Broadens jsonRefused to catch gateway parameter errors (400 Provider returned error, 404/400 No endpoints found that support the provided parameters, invalid schema) so known.json steps down ("schema" → "object" → "prompt") instead of failing with a generic provider or unknown-model error.
  3. Thinking-token headroom on reasoning models (THINKING_TOKENS):
    • When a model returns reasoning output (reasoning_content, reasoning, thinking chunks, or reasoning_tokens > 0), remembers known.thinks = true and adds 12,000 tokens of headroom (matching AnthropicProvider) so reasoning models on OpenCode and OpenRouter don't get cut off at 4,096 tokens mid-thought (finish_reason: "length"). If the first call is cut off while reasoning, it retries once with the added budget.

Testing

  • yarn vitest run src/lib/ai/providers.test.ts src/lib/ai/structured-output.test.ts (74 tests pass)
  • yarn typecheck
  • yarn lint

@AienTech

Copy link
Copy Markdown
Owner

thanks @akrista for the PR. I'll take a closer look into it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants