Skip to content

Commit 8f4d29c

Browse files
author
decolua
committed
# v0.4.30 (2026-05-11)
## Features - MCP stdio→SSE bridge: expose local stdio MCP plugins over SSE (api/mcp/[plugin]/sse, /message) - Dynamic Linux cert resolution + NSS DB injection (Debian/Arch/Fedora/openSUSE, Chrome/Chromium/Firefox incl. snap) (#1010) - Cowork tool: expanded settings UI & API - GitBook docs (DocsContent, DocsLayout) ## Fixes - OAuth callback postMessage scoped to expected origins (CWE-1385) (#998) - Re-enable TLS verification on DNS-bypass fetch (CWE-295) (#998) - Normalize `developer` role → `system` for OpenAI-format providers (Deepseek, Groq, …) (#1011, closes #773) - Respect `PORT` env in internal model-test fetch (#1014) - Dropdown text readability in dark theme on usage page (#997) ## Improvements - Refactor Claude CLI spoof headers into shared constant - Tool deduper utility in open-sse handlers
1 parent 76f3d4b commit 8f4d29c

23 files changed

Lines changed: 1200 additions & 157 deletions

File tree

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
# v0.4.30 (2026-05-11)
2+
3+
## Features
4+
- MCP stdio→SSE bridge: expose local stdio MCP plugins over SSE (api/mcp/[plugin]/sse, /message)
5+
- Dynamic Linux cert resolution + NSS DB injection (Debian/Arch/Fedora/openSUSE, Chrome/Chromium/Firefox incl. snap) (#1010)
6+
- Cowork tool: expanded settings UI & API
7+
- GitBook docs (DocsContent, DocsLayout)
8+
9+
## Fixes
10+
- OAuth callback postMessage scoped to expected origins (CWE-1385) (#998)
11+
- Re-enable TLS verification on DNS-bypass fetch (CWE-295) (#998)
12+
- Normalize `developer` role → `system` for OpenAI-format providers (Deepseek, Groq, …) (#1011, closes #773)
13+
- Respect `PORT` env in internal model-test fetch (#1014)
14+
- Dropdown text readability in dark theme on usage page (#997)
15+
16+
## Improvements
17+
- Refactor Claude CLI spoof headers into shared constant
18+
- Tool deduper utility in open-sse handlers
19+
120
# v0.4.29 (2026-05-10)
221

322
## Features

gitbook/components/DocsContent.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { MarkdownRenderer } from "@/utils/markdown";
44

55
export default function DocsContent({ content }) {
66
return (
7-
<main className="flex-1 overflow-y-auto">
8-
<article className="max-w-4xl mx-auto px-6 py-8">
7+
<main className="flex-1 min-w-0 overflow-x-hidden overflow-y-auto">
8+
<article className="max-w-4xl mx-auto px-4 sm:px-6 py-8">
99
<MarkdownRenderer content={content} />
1010
</article>
1111
</main>

gitbook/components/DocsLayout.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ export default function DocsLayout({ children, headings = [], lang = DEFAULT_LAN
1010
<div className="min-h-screen flex flex-col bg-[#FCFBF9]">
1111
<DocsHeader lang={lang} />
1212
<div className="flex-1 flex">
13-
{/* Desktop sidebar */}
1413
<div className="hidden lg:block">
1514
<DocsSidebar lang={lang} />
1615
</div>
17-
18-
<div className="flex-1 flex">
16+
<div className="flex-1 flex min-w-0">
1917
{children}
20-
<DocsToc headings={headings} lang={lang} />
18+
<div className="hidden lg:block">
19+
<DocsToc headings={headings} lang={lang} />
20+
</div>
2121
</div>
2222
</div>
2323
</div>

next.config.mjs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { fileURLToPath } from "node:url";
2-
import { dirname } from "node:path";
2+
import { dirname, resolve } from "node:path";
33

44
const projectRoot = dirname(fileURLToPath(import.meta.url));
5+
const monorepoRoot = resolve(projectRoot, "..");
56

67
/** @type {import('next').NextConfig} */
78
const nextConfig = {
@@ -10,9 +11,9 @@ const nextConfig = {
1011
turbopack: {
1112
root: projectRoot
1213
},
13-
outputFileTracingRoot: projectRoot,
14+
outputFileTracingRoot: monorepoRoot,
1415
outputFileTracingExcludes: {
15-
"*": ["./gitbook/**/*"]
16+
"*": ["./app/gitbook/**/*", "./gitbook/**/*"]
1617
},
1718
images: {
1819
unoptimized: true

open-sse/config/providerModels.js

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,152 @@ export const PROVIDER_MODELS = {
601601
{ id: "openai/whisper-large-v3", name: "Whisper Large v3 (HF)", type: "stt", params: ["language"] },
602602
{ id: "openai/whisper-small", name: "Whisper Small (HF)", type: "stt", params: ["language"] },
603603
],
604+
605+
// === Free-tier providers (synced from OmniRoute) ===
606+
agentrouter: [
607+
{ id: "claude-opus-4-6", name: "Claude 4.6 Opus" },
608+
{ id: "claude-haiku-4-5-20251001", name: "Claude 4.5 Haiku" },
609+
{ id: "glm-5.1", name: "GLM 5.1" },
610+
{ id: "deepseek-v3.2", name: "DeepSeek V3.2" },
611+
],
612+
aimlapi: [
613+
{ id: "gpt-4o", name: "GPT-4o" },
614+
{ id: "gpt-4o-mini", name: "GPT-4o Mini" },
615+
{ id: "claude-3-5-sonnet-20241022", name: "Claude 3.5 Sonnet" },
616+
{ id: "gemini-2.0-flash-exp", name: "Gemini 2.0 Flash" },
617+
{ id: "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo", name: "Llama 3.1 70B" },
618+
],
619+
novita: [
620+
{ id: "deepseek/deepseek-r1", name: "DeepSeek R1" },
621+
{ id: "deepseek/deepseek-v3", name: "DeepSeek V3" },
622+
{ id: "meta-llama/llama-3.3-70b-instruct", name: "Llama 3.3 70B" },
623+
{ id: "qwen/qwen-2.5-72b-instruct", name: "Qwen 2.5 72B" },
624+
],
625+
modal: [
626+
{ id: "auto", name: "Auto (User-hosted)" },
627+
],
628+
reka: [
629+
{ id: "reka-flash-3", name: "Reka Flash 3" },
630+
{ id: "reka-edge-2603", name: "Reka Edge 2603" },
631+
],
632+
nlpcloud: [
633+
{ id: "chatdolphin", name: "ChatDolphin" },
634+
{ id: "dolphin", name: "Dolphin" },
635+
{ id: "finetuned-llama-3-70b", name: "Llama 3 70B (Finetuned)" },
636+
],
637+
bazaarlink: [
638+
{ id: "auto:free", name: "Auto Free (Zero Cost)" },
639+
{ id: "auto", name: "Auto (Best Model)" },
640+
],
641+
completions: [
642+
{ id: "claude-opus-4", name: "Claude Opus 4" },
643+
{ id: "claude-sonnet-4", name: "Claude Sonnet 4" },
644+
{ id: "gpt-4o", name: "GPT-4o" },
645+
{ id: "gemini-2.0-flash", name: "Gemini 2.0 Flash" },
646+
],
647+
enally: [
648+
{ id: "gpt-4o", name: "GPT-4o" },
649+
{ id: "gpt-4o-mini", name: "GPT-4o Mini" },
650+
{ id: "claude-3-5-sonnet", name: "Claude 3.5 Sonnet" },
651+
],
652+
freetheai: [
653+
{ id: "gpt-4o", name: "GPT-4o" },
654+
{ id: "claude-3-5-sonnet", name: "Claude 3.5 Sonnet" },
655+
{ id: "gemini-1.5-pro", name: "Gemini 1.5 Pro" },
656+
{ id: "deepseek-chat", name: "DeepSeek Chat" },
657+
],
658+
llm7: [
659+
{ id: "gpt-4o-mini", name: "GPT-4o Mini" },
660+
{ id: "gpt-4.1-mini", name: "GPT-4.1 Mini" },
661+
{ id: "gemini-1.5-flash", name: "Gemini 1.5 Flash" },
662+
],
663+
lepton: [
664+
{ id: "llama3-1-405b", name: "Llama 3.1 405B" },
665+
{ id: "llama3-1-70b", name: "Llama 3.1 70B" },
666+
{ id: "llama3-1-8b", name: "Llama 3.1 8B" },
667+
{ id: "mixtral-8x7b", name: "Mixtral 8x7B" },
668+
],
669+
kluster: [
670+
{ id: "deepseek-ai/DeepSeek-R1", name: "DeepSeek R1" },
671+
{ id: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", name: "Llama 4 Maverick" },
672+
{ id: "meta-llama/Llama-4-Scout-17B-16E-Instruct", name: "Llama 4 Scout" },
673+
{ id: "Qwen/Qwen3-235B-A22B-Instruct", name: "Qwen3 235B" },
674+
],
675+
ai21: [
676+
{ id: "jamba-large", name: "Jamba 1.5 Large" },
677+
{ id: "jamba-mini", name: "Jamba 1.5 Mini" },
678+
],
679+
"inference-net": [
680+
{ id: "meta-llama/llama-3.3-70b-instruct/fp-16", name: "Llama 3.3 70B" },
681+
{ id: "deepseek/deepseek-v3-0324", name: "DeepSeek V3" },
682+
{ id: "mistralai/mistral-nemo-12b-instruct/fp-16", name: "Mistral Nemo 12B" },
683+
],
684+
predibase: [
685+
{ id: "llama-3-2-3b-instruct", name: "Llama 3.2 3B" },
686+
{ id: "llama-3-1-8b-instruct", name: "Llama 3.1 8B" },
687+
{ id: "qwen2-5-7b-instruct", name: "Qwen 2.5 7B" },
688+
],
689+
bytez: [
690+
{ id: "meta-llama/Llama-3.3-70B-Instruct", name: "Llama 3.3 70B" },
691+
{ id: "mistralai/Mistral-7B-Instruct-v0.3", name: "Mistral 7B v0.3" },
692+
{ id: "Qwen/Qwen2.5-72B-Instruct", name: "Qwen 2.5 72B" },
693+
],
694+
morph: [
695+
{ id: "morph-v3-large", name: "Morph V3 Large" },
696+
{ id: "morph-v3-fast", name: "Morph V3 Fast" },
697+
],
698+
longcat: [
699+
{ id: "LongCat-Flash-Chat", name: "LongCat Flash Chat" },
700+
{ id: "LongCat-Flash-Thinking", name: "LongCat Flash Thinking" },
701+
{ id: "LongCat-Flash-Lite", name: "LongCat Flash Lite" },
702+
],
703+
puter: [
704+
{ id: "gpt-5", name: "GPT-5" },
705+
{ id: "claude-opus-4", name: "Claude Opus 4" },
706+
{ id: "gemini-3-pro-preview", name: "Gemini 3 Pro" },
707+
{ id: "grok-4", name: "Grok 4" },
708+
{ id: "deepseek-chat", name: "DeepSeek V3" },
709+
],
710+
uncloseai: [
711+
{ id: "auto", name: "Auto (Free)" },
712+
{ id: "gpt-4o-mini", name: "GPT-4o Mini" },
713+
],
714+
scaleway: [
715+
{ id: "qwen3-235b-a22b-instruct-2507", name: "Qwen3 235B" },
716+
{ id: "llama-3.3-70b-instruct", name: "Llama 3.3 70B" },
717+
{ id: "mistral-small-3.1-24b-instruct-2503", name: "Mistral Small 3.1" },
718+
],
719+
deepinfra: [
720+
{ id: "meta-llama/Meta-Llama-3.1-70B-Instruct", name: "Llama 3.1 70B" },
721+
{ id: "deepseek-ai/DeepSeek-V3", name: "DeepSeek V3" },
722+
{ id: "Qwen/Qwen2.5-72B-Instruct", name: "Qwen 2.5 72B" },
723+
],
724+
sambanova: [
725+
{ id: "Meta-Llama-3.1-405B-Instruct", name: "Llama 3.1 405B" },
726+
{ id: "Meta-Llama-3.1-70B-Instruct", name: "Llama 3.1 70B" },
727+
{ id: "Meta-Llama-3.1-8B-Instruct", name: "Llama 3.1 8B" },
728+
],
729+
nscale: [
730+
{ id: "meta-llama/Llama-3.3-70B-Instruct", name: "Llama 3.3 70B" },
731+
{ id: "Qwen/Qwen2.5-Coder-32B-Instruct", name: "Qwen 2.5 Coder 32B" },
732+
],
733+
baseten: [
734+
{ id: "deepseek-ai/DeepSeek-R1", name: "DeepSeek R1" },
735+
{ id: "meta-llama/Llama-3.3-70B-Instruct", name: "Llama 3.3 70B" },
736+
],
737+
publicai: [
738+
{ id: "auto", name: "Auto (Community)" },
739+
],
740+
"nous-research": [
741+
{ id: "Hermes-4-405B", name: "Hermes 4 405B" },
742+
{ id: "Hermes-4-70B", name: "Hermes 4 70B" },
743+
],
744+
glhf: [
745+
{ id: "hf:meta-llama/Meta-Llama-3.1-405B-Instruct", name: "Llama 3.1 405B" },
746+
{ id: "hf:meta-llama/Meta-Llama-3.1-70B-Instruct", name: "Llama 3.1 70B" },
747+
{ id: "hf:Qwen/Qwen2.5-72B-Instruct", name: "Qwen 2.5 72B" },
748+
],
749+
604750
deepgram: [
605751
{ id: "nova-3", name: "Nova 3", type: "stt", params: ["language"] },
606752
{ id: "nova-2", name: "Nova 2", type: "stt", params: ["language"] },

open-sse/config/providers.js

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,32 @@ const CLAUDE_API_HEADERS = {
2626
"Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14"
2727
};
2828

29+
// Full Claude CLI fingerprint — required by providers that gate on client identity (e.g. agentrouter)
30+
const CLAUDE_CLI_SPOOF_HEADERS = {
31+
"Anthropic-Version": "2023-06-01",
32+
"Anthropic-Beta": "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05,advanced-tool-use-2025-11-20,effort-2025-11-24,structured-outputs-2025-12-15,fast-mode-2026-02-01,redact-thinking-2026-02-12,token-efficient-tools-2026-03-28",
33+
"Anthropic-Dangerous-Direct-Browser-Access": "true",
34+
"User-Agent": "claude-cli/2.1.92 (external, sdk-cli)",
35+
"X-App": "cli",
36+
"X-Stainless-Helper-Method": "stream",
37+
"X-Stainless-Retry-Count": "0",
38+
"X-Stainless-Runtime-Version": "v24.14.0",
39+
"X-Stainless-Package-Version": "0.80.0",
40+
"X-Stainless-Runtime": "node",
41+
"X-Stainless-Lang": "js",
42+
"X-Stainless-Arch": mapStainlessArch(),
43+
"X-Stainless-Os": mapStainlessOs(),
44+
"X-Stainless-Timeout": "600"
45+
};
46+
2947
// Shared baseUrls
3048
const KIMI_CODING_BASE_URL = "https://api.kimi.com/coding/v1/messages";
3149

3250
export const PROVIDERS = {
3351
claude: {
3452
baseUrl: "https://api.anthropic.com/v1/messages",
3553
format: "claude",
36-
headers: {
37-
"Anthropic-Version": "2023-06-01",
38-
"Anthropic-Beta": "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05,advanced-tool-use-2025-11-20,effort-2025-11-24,structured-outputs-2025-12-15,fast-mode-2026-02-01,redact-thinking-2026-02-12,token-efficient-tools-2026-03-28",
39-
"Anthropic-Dangerous-Direct-Browser-Access": "true",
40-
"User-Agent": "claude-cli/2.1.92 (external, sdk-cli)",
41-
"X-App": "cli",
42-
"X-Stainless-Helper-Method": "stream",
43-
"X-Stainless-Retry-Count": "0",
44-
"X-Stainless-Runtime-Version": "v24.14.0",
45-
"X-Stainless-Package-Version": "0.80.0",
46-
"X-Stainless-Runtime": "node",
47-
"X-Stainless-Lang": "js",
48-
"X-Stainless-Arch": mapStainlessArch(),
49-
"X-Stainless-Os": mapStainlessOs(),
50-
"X-Stainless-Timeout": "600"
51-
},
54+
headers: { ...CLAUDE_CLI_SPOOF_HEADERS },
5255
clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
5356
tokenUrl: "https://api.anthropic.com/v1/oauth/token"
5457
},
@@ -384,6 +387,39 @@ export const PROVIDERS = {
384387
baseUrl: "https://api.xiaomimimo.com/v1/chat/completions",
385388
format: "openai"
386389
},
390+
// === Free-tier providers (synced from OmniRoute) ===
391+
// Claude-format with Claude CLI header spoofing (auth: x-api-key)
392+
agentrouter: { baseUrl: "https://agentrouter.org/v1/messages", format: "claude", headers: { ...CLAUDE_CLI_SPOOF_HEADERS } },
393+
// OpenAI-compatible (auth: bearer)
394+
aimlapi: { baseUrl: "https://api.aimlapi.com/v1/chat/completions", format: "openai" },
395+
novita: { baseUrl: "https://api.novita.ai/v3/openai/chat/completions", format: "openai" },
396+
modal: { baseUrl: "https://api.modal.com/v1/chat/completions", format: "openai" },
397+
reka: { baseUrl: "https://api.reka.ai/v1/chat/completions", format: "openai" },
398+
nlpcloud: { baseUrl: "https://api.nlpcloud.io/v1/gpu/chatbot", format: "openai" },
399+
bazaarlink: { baseUrl: "https://bazaarlink.ai/api/v1/chat/completions", format: "openai" },
400+
completions: { baseUrl: "https://completions.me/api/v1/chat/completions", format: "openai" },
401+
// enally uses X-API-Key header (not bearer); handled in validate route
402+
enally: { baseUrl: "https://ai.enally.in/v1/chat/completions", format: "openai", authHeader: "x-api-key" },
403+
freetheai: { baseUrl: "https://api.freetheai.xyz/v1/chat/completions", format: "openai" },
404+
llm7: { baseUrl: "https://api.llm7.io/v1/chat/completions", format: "openai" },
405+
lepton: { baseUrl: "https://api.lepton.ai/api/v1/chat/completions", format: "openai" },
406+
kluster: { baseUrl: "https://api.kluster.ai/v1/chat/completions", format: "openai" },
407+
ai21: { baseUrl: "https://api.ai21.com/studio/v1/chat/completions", format: "openai" },
408+
"inference-net": { baseUrl: "https://api.inference.net/v1/chat/completions", format: "openai" },
409+
predibase: { baseUrl: "https://serving.app.predibase.com/v1/chat/completions", format: "openai" },
410+
bytez: { baseUrl: "https://api.bytez.com/models/v2", format: "openai" },
411+
morph: { baseUrl: "https://api.morphllm.com/v1/chat/completions", format: "openai" },
412+
longcat: { baseUrl: "https://api.longcat.chat/openai/v1/chat/completions", format: "openai" },
413+
puter: { baseUrl: "https://api.puter.com/puterai/openai/v1/chat/completions", format: "openai" },
414+
uncloseai: { baseUrl: "https://hermes.ai.unturf.com/v1/chat/completions", format: "openai", noAuth: true },
415+
scaleway: { baseUrl: "https://api.scaleway.ai/v1/chat/completions", format: "openai" },
416+
deepinfra: { baseUrl: "https://api.deepinfra.com/v1/openai/chat/completions", format: "openai" },
417+
sambanova: { baseUrl: "https://api.sambanova.ai/v1/chat/completions", format: "openai" },
418+
nscale: { baseUrl: "https://inference.api.nscale.com/v1/chat/completions", format: "openai" },
419+
baseten: { baseUrl: "https://inference.baseten.co/v1/chat/completions", format: "openai" },
420+
publicai: { baseUrl: "https://api.publicai.co/v1/chat/completions", format: "openai" },
421+
"nous-research": { baseUrl: "https://inference-api.nousresearch.com/v1/chat/completions", format: "openai" },
422+
glhf: { baseUrl: "https://glhf.chat/api/openai/v1/chat/completions", format: "openai" },
387423
};
388424

389425
export const OLLAMA_LOCAL_DEFAULT_HOST = "http://localhost:11434";

open-sse/executors/default.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,9 @@ export class DefaultExecutor extends BaseExecutor {
9696
case "kimi":
9797
case "minimax":
9898
case "minimax-cn":
99-
headers["x-api-key"] = credentials.apiKey || credentials.accessToken;
100-
break;
10199
case "kimi-coding":
102-
headers["Authorization"] = `Bearer ${credentials.accessToken}`;
103-
Object.assign(headers, buildKimiHeaders());
100+
headers["x-api-key"] = credentials.apiKey || credentials.accessToken;
101+
if (this.provider === "kimi-coding") Object.assign(headers, buildKimiHeaders());
104102
break;
105103
default:
106104
if (this.provider?.startsWith?.("anthropic-compatible-")) {
@@ -124,6 +122,10 @@ export class DefaultExecutor extends BaseExecutor {
124122
}
125123
} else if (this.provider === "cline") {
126124
Object.assign(headers, buildClineHeaders(credentials.apiKey || credentials.accessToken));
125+
} else if (this.config?.format === "claude") {
126+
// Generic claude-format provider (e.g. agentrouter): x-api-key + anthropic-version
127+
headers["x-api-key"] = credentials.apiKey || credentials.accessToken;
128+
if (!headers["anthropic-version"]) headers["anthropic-version"] = "2023-06-01";
127129
} else {
128130
headers["Authorization"] = `Bearer ${credentials.apiKey || credentials.accessToken}`;
129131
}

open-sse/handlers/chatCore.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { handleForcedSSEToJson } from "./chatCore/sseToJsonHandler.js";
1616
import { handleNonStreamingResponse } from "./chatCore/nonStreamingHandler.js";
1717
import { handleStreamingResponse, buildOnStreamComplete } from "./chatCore/streamingHandler.js";
1818
import { detectClientTool, isNativePassthrough } from "../utils/clientDetector.js";
19+
import { dedupeTools } from "../utils/toolDeduper.js";
1920
import { injectCaveman } from "../rtk/caveman.js";
2021
import { compressMessages, formatRtkLog } from "../rtk/index.js";
2122

@@ -94,6 +95,15 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
9495
translatedBody.model = model;
9596
}
9697

98+
// Dedupe duplicate built-in tools when equivalent MCP tools are present (Claude clients only).
99+
if (clientTool === "claude" && Array.isArray(translatedBody.tools)) {
100+
const { tools: deduped, stripped } = dedupeTools(translatedBody.tools);
101+
if (stripped.length > 0) {
102+
translatedBody.tools = deduped;
103+
log?.debug?.("TOOLDEDUP", `stripped ${stripped.length}: ${stripped.slice(0, 3).join(", ")}${stripped.length > 3 ? "..." : ""}`);
104+
}
105+
}
106+
97107
// Token savers: applied at the final body just before dispatch
98108
// Covers both passthrough (source shape) and translated (target shape) flows
99109
const finalFormat = passthrough ? sourceFormat : targetFormat;

0 commit comments

Comments
 (0)