From 139601c5bf71aece59a3e6851cd1912233af16c4 Mon Sep 17 00:00:00 2001
From: Sun-sunshine06
Date: Thu, 23 Apr 2026 13:32:24 +0800
Subject: [PATCH 1/4] fix: enable reasoning for third-party OpenAI-compatible
proxies (issue #188)
Previously, only enabled reasoning=true for official OpenAI
API when the modelId matched a reasoning pattern. This commit extends the
heuristic to also match reasoning-capable model IDs on any third-party
OpenAI-compatible proxy (univibe, OpenRouter custom endpoint, etc).
Fixes issue #188 where configured a proxy with OpenAI-compatible protocol
and a Claude 4 reasoning model would return 0 artifacts because the reasoning
flag was missing, causing the gateway to reject the request or return empty.
Co-Authored-By: Claude Opus 4.7
---
packages/providers/src/index.test.ts | 18 ++++++++++++++++++
packages/providers/src/index.ts | 27 ++++++++++++++++++++++++++-
2 files changed, 44 insertions(+), 1 deletion(-)
diff --git a/packages/providers/src/index.test.ts b/packages/providers/src/index.test.ts
index e05e30ae..ebdeaf80 100644
--- a/packages/providers/src/index.test.ts
+++ b/packages/providers/src/index.test.ts
@@ -555,4 +555,22 @@ describe('inferReasoning', () => {
it('returns false when wire is undefined', () => {
expect(inferReasoning(undefined, 'gpt-4o', 'https://api.openai.com/v1')).toBe(false);
});
+
+ it('returns true for third-party openai-chat with reasoning model ID (issue #188)', () => {
+ // univibe/custom proxy with Claude 4 model
+ expect(inferReasoning('openai-chat', 'claude-opus-4-6', 'https://api.univibe.cc/openai')).toBe(true);
+ expect(inferReasoning('openai-chat', 'claude-sonnet-4-6', 'https://api.univibe.cc/openai')).toBe(true);
+ // OpenRouter-style paths on custom proxy
+ expect(inferReasoning('openai-chat', 'anthropic/claude-opus-4-6', 'https://my-proxy.example/v1')).toBe(true);
+ // o1 on custom proxy
+ expect(inferReasoning('openai-chat', 'o1-mini', 'https://my-proxy.example/v1')).toBe(true);
+ // qwen/qwq on custom proxy
+ expect(inferReasoning('openai-chat', 'qwen/qwq-32b-preview', 'https://my-proxy.example/v1')).toBe(true);
+ });
+
+ it('returns false for third-party openai-chat with non-reasoning model ID', () => {
+ expect(inferReasoning('openai-chat', 'qwen3.6-plus', 'https://dashscope.aliyuncs.com/compatible-mode/v1')).toBe(false);
+ expect(inferReasoning('openai-chat', 'deepseek-chat', 'https://api.deepseek.com/v1')).toBe(false);
+ expect(inferReasoning('openai-chat', 'glm-4.6v', 'https://open.bigmodel.cn/api/paas/v4')).toBe(false);
+ });
});
diff --git a/packages/providers/src/index.ts b/packages/providers/src/index.ts
index 940b103e..c4e62949 100644
--- a/packages/providers/src/index.ts
+++ b/packages/providers/src/index.ts
@@ -189,6 +189,24 @@ function isReasoningModelId(modelId: string): boolean {
return /^(o[134]|gpt-5)/i.test(modelId);
}
+/**
+ * Matches reasoning-capable model IDs commonly proxied through OpenAI-compatible
+ * gateways (OpenRouter, univibe, sub2api, etc). This pattern matches the same
+ * set that OPENROUTER_REASONING_MODEL_RE uses for OpenRouter, but applies to
+ * custom openai-chat wire endpoints as well.
+ */
+const REASONING_MODEL_ID_PATTERN = new RegExp(
+ [
+ ':thinking$',
+ '(^|/)claude-(?:opus|sonnet)-4',
+ '^o1[-.]', '^o3[-.]', '^o4[-.]', '^gpt-5[-.]',
+ '^minimax/minimax-m\\d',
+ '^deepseek/deepseek-r\\d',
+ '^qwen/qwq',
+ ].join('|'),
+ 'i',
+);
+
export function inferReasoning(
wire: GenerateOptions['wire'],
modelId: string,
@@ -201,7 +219,14 @@ export function inferReasoning(
case 'openai-codex-responses':
return true;
case 'openai-chat':
- return isOpenAIOfficial(baseUrl) && isReasoningModelId(modelId);
+ // For official OpenAI, check both base URL and model ID pattern
+ if (isOpenAIOfficial(baseUrl)) {
+ return isReasoningModelId(modelId);
+ }
+ // For third-party OpenAI-compatible gateways, heuristically match
+ // common reasoning model IDs — many gateways still require the
+ // reasoning flag to get extended thinking output.
+ return REASONING_MODEL_ID_PATTERN.test(modelId);
default:
return false;
}
From 8abe2ae40d93d0feb0591b81bf9cc2dd0970dcac Mon Sep 17 00:00:00 2001
From: Sun-sunshine06
Date: Thu, 23 Apr 2026 13:57:12 +0800
Subject: [PATCH 2/4] fix: cover namespaced openai reasoning models on proxies
Signed-off-by: Sun-sunshine06
---
packages/providers/src/index.test.ts | 39 +++++++++++++++++++++++-----
packages/providers/src/index.ts | 2 +-
2 files changed, 33 insertions(+), 8 deletions(-)
diff --git a/packages/providers/src/index.test.ts b/packages/providers/src/index.test.ts
index ebdeaf80..dbf73877 100644
--- a/packages/providers/src/index.test.ts
+++ b/packages/providers/src/index.test.ts
@@ -558,19 +558,44 @@ describe('inferReasoning', () => {
it('returns true for third-party openai-chat with reasoning model ID (issue #188)', () => {
// univibe/custom proxy with Claude 4 model
- expect(inferReasoning('openai-chat', 'claude-opus-4-6', 'https://api.univibe.cc/openai')).toBe(true);
- expect(inferReasoning('openai-chat', 'claude-sonnet-4-6', 'https://api.univibe.cc/openai')).toBe(true);
+ expect(inferReasoning('openai-chat', 'claude-opus-4-6', 'https://api.univibe.cc/openai')).toBe(
+ true,
+ );
+ expect(
+ inferReasoning('openai-chat', 'claude-sonnet-4-6', 'https://api.univibe.cc/openai'),
+ ).toBe(true);
// OpenRouter-style paths on custom proxy
- expect(inferReasoning('openai-chat', 'anthropic/claude-opus-4-6', 'https://my-proxy.example/v1')).toBe(true);
+ expect(
+ inferReasoning('openai-chat', 'anthropic/claude-opus-4-6', 'https://my-proxy.example/v1'),
+ ).toBe(true);
+ // OpenAI-style namespaced paths on custom proxy
+ expect(inferReasoning('openai-chat', 'openai/o3-mini', 'https://my-proxy.example/v1')).toBe(
+ true,
+ );
+ expect(inferReasoning('openai-chat', 'openai/gpt-5.1', 'https://my-proxy.example/v1')).toBe(
+ true,
+ );
// o1 on custom proxy
expect(inferReasoning('openai-chat', 'o1-mini', 'https://my-proxy.example/v1')).toBe(true);
// qwen/qwq on custom proxy
- expect(inferReasoning('openai-chat', 'qwen/qwq-32b-preview', 'https://my-proxy.example/v1')).toBe(true);
+ expect(
+ inferReasoning('openai-chat', 'qwen/qwq-32b-preview', 'https://my-proxy.example/v1'),
+ ).toBe(true);
});
it('returns false for third-party openai-chat with non-reasoning model ID', () => {
- expect(inferReasoning('openai-chat', 'qwen3.6-plus', 'https://dashscope.aliyuncs.com/compatible-mode/v1')).toBe(false);
- expect(inferReasoning('openai-chat', 'deepseek-chat', 'https://api.deepseek.com/v1')).toBe(false);
- expect(inferReasoning('openai-chat', 'glm-4.6v', 'https://open.bigmodel.cn/api/paas/v4')).toBe(false);
+ expect(
+ inferReasoning(
+ 'openai-chat',
+ 'qwen3.6-plus',
+ 'https://dashscope.aliyuncs.com/compatible-mode/v1',
+ ),
+ ).toBe(false);
+ expect(inferReasoning('openai-chat', 'deepseek-chat', 'https://api.deepseek.com/v1')).toBe(
+ false,
+ );
+ expect(inferReasoning('openai-chat', 'glm-4.6v', 'https://open.bigmodel.cn/api/paas/v4')).toBe(
+ false,
+ );
});
});
diff --git a/packages/providers/src/index.ts b/packages/providers/src/index.ts
index c4e62949..29c1140b 100644
--- a/packages/providers/src/index.ts
+++ b/packages/providers/src/index.ts
@@ -199,7 +199,7 @@ const REASONING_MODEL_ID_PATTERN = new RegExp(
[
':thinking$',
'(^|/)claude-(?:opus|sonnet)-4',
- '^o1[-.]', '^o3[-.]', '^o4[-.]', '^gpt-5[-.]',
+ '^(?:openai/)?(?:o1|o3|o4|gpt-5)(?:[-.].*)?$',
'^minimax/minimax-m\\d',
'^deepseek/deepseek-r\\d',
'^qwen/qwq',
From c0815120b3a34085b9d21c614f7d5242c2af5ffe Mon Sep 17 00:00:00 2001
From: Sun-sunshine06
Date: Thu, 23 Apr 2026 14:17:08 +0800
Subject: [PATCH 3/4] feat: warn about coding plan app allowlists
Signed-off-by: Sun-sunshine06
---
.../AddCustomProviderModal.test.tsx | 39 ++++++++++++++
.../src/components/AddCustomProviderModal.tsx | 11 ++++
.../ConnectionDiagnosticPanel.test.ts | 32 ++++++++++-
.../components/ConnectionDiagnosticPanel.tsx | 54 +++++++++++++++++++
packages/i18n/src/locales/en.json | 4 ++
packages/i18n/src/locales/pt-BR.json | 4 ++
packages/i18n/src/locales/zh-CN.json | 4 ++
7 files changed, 147 insertions(+), 1 deletion(-)
create mode 100644 apps/desktop/src/renderer/src/components/AddCustomProviderModal.test.tsx
diff --git a/apps/desktop/src/renderer/src/components/AddCustomProviderModal.test.tsx b/apps/desktop/src/renderer/src/components/AddCustomProviderModal.test.tsx
new file mode 100644
index 00000000..7698ad33
--- /dev/null
+++ b/apps/desktop/src/renderer/src/components/AddCustomProviderModal.test.tsx
@@ -0,0 +1,39 @@
+import { renderToStaticMarkup } from 'react-dom/server';
+import { describe, expect, it, vi } from 'vitest';
+import { AddCustomProviderModal } from './AddCustomProviderModal';
+
+vi.mock('@open-codesign/i18n', () => ({
+ useT: () => (key: string) => key,
+}));
+
+describe('AddCustomProviderModal', () => {
+ it('shows the compatibility warning for editable custom endpoints', () => {
+ const html = renderToStaticMarkup(
+ undefined} onClose={() => undefined} />,
+ );
+
+ expect(html).toContain('settings.providers.custom.compatibilityHintTitle');
+ expect(html).toContain('settings.providers.custom.compatibilityHintBody');
+ });
+
+ it('hides the compatibility warning when editing a locked builtin endpoint', () => {
+ const html = renderToStaticMarkup(
+ undefined}
+ onClose={() => undefined}
+ editTarget={{
+ id: 'anthropic',
+ name: 'Anthropic',
+ baseUrl: 'https://api.anthropic.com',
+ wire: 'anthropic',
+ defaultModel: 'claude-sonnet-4-5',
+ builtin: true,
+ lockEndpoint: true,
+ }}
+ />,
+ );
+
+ expect(html).not.toContain('settings.providers.custom.compatibilityHintTitle');
+ expect(html).not.toContain('settings.providers.custom.compatibilityHintBody');
+ });
+});
diff --git a/apps/desktop/src/renderer/src/components/AddCustomProviderModal.tsx b/apps/desktop/src/renderer/src/components/AddCustomProviderModal.tsx
index 32eb7abe..4d1326c5 100644
--- a/apps/desktop/src/renderer/src/components/AddCustomProviderModal.tsx
+++ b/apps/desktop/src/renderer/src/components/AddCustomProviderModal.tsx
@@ -326,6 +326,17 @@ export function AddCustomProviderModal({
placeholder="https://api.example.com/v1"
disabled={lockEndpoint}
/>
+ {!lockEndpoint && (
+
+
+
+
{t('settings.providers.custom.compatibilityHintTitle')}
+
+
+ {t('settings.providers.custom.compatibilityHintBody')}
+
+
+ )}
diff --git a/apps/desktop/src/renderer/src/components/ConnectionDiagnosticPanel.test.ts b/apps/desktop/src/renderer/src/components/ConnectionDiagnosticPanel.test.ts
index e18b7153..b9a15681 100644
--- a/apps/desktop/src/renderer/src/components/ConnectionDiagnosticPanel.test.ts
+++ b/apps/desktop/src/renderer/src/components/ConnectionDiagnosticPanel.test.ts
@@ -8,7 +8,7 @@ vi.mock('../store', () => ({
useCodesignStore: () => vi.fn(),
}));
-import { isAbsoluteHttpUrl } from './ConnectionDiagnosticPanel';
+import { isAbsoluteHttpUrl, shouldShowGatewayAllowlistHint } from './ConnectionDiagnosticPanel';
describe('isAbsoluteHttpUrl', () => {
it('rejects an empty string so /v1 quick-fix cannot produce a bare "/v1"', () => {
@@ -28,3 +28,33 @@ describe('isAbsoluteHttpUrl', () => {
expect(isAbsoluteHttpUrl(' https://api.example.com ')).toBe(true);
});
});
+
+describe('shouldShowGatewayAllowlistHint', () => {
+ it('shows the hint for 400-class compatibility failures on third-party gateways', () => {
+ expect(shouldShowGatewayAllowlistHint('400', 'https://relay.example.com/v1', undefined)).toBe(
+ true,
+ );
+ expect(
+ shouldShowGatewayAllowlistHint(
+ '403',
+ 'https://relay.example.com/v1',
+ 'https://relay.example.com/v1/chat/completions',
+ ),
+ ).toBe(true);
+ expect(shouldShowGatewayAllowlistHint('PARSE', 'https://relay.example.com/v1')).toBe(true);
+ });
+
+ it('suppresses the hint for official providers and localhost proxies', () => {
+ expect(shouldShowGatewayAllowlistHint('400', 'https://api.openai.com/v1')).toBe(false);
+ expect(shouldShowGatewayAllowlistHint('403', 'https://api.anthropic.com')).toBe(false);
+ expect(shouldShowGatewayAllowlistHint('400', 'http://127.0.0.1:8317')).toBe(false);
+ });
+
+ it('suppresses the hint for unrelated error classes', () => {
+ expect(shouldShowGatewayAllowlistHint('404', 'https://relay.example.com/v1')).toBe(false);
+ expect(shouldShowGatewayAllowlistHint('429', 'https://relay.example.com/v1')).toBe(false);
+ expect(shouldShowGatewayAllowlistHint('ECONNREFUSED', 'https://relay.example.com/v1')).toBe(
+ false,
+ );
+ });
+});
diff --git a/apps/desktop/src/renderer/src/components/ConnectionDiagnosticPanel.tsx b/apps/desktop/src/renderer/src/components/ConnectionDiagnosticPanel.tsx
index 57b5b077..44b6d8fe 100644
--- a/apps/desktop/src/renderer/src/components/ConnectionDiagnosticPanel.tsx
+++ b/apps/desktop/src/renderer/src/components/ConnectionDiagnosticPanel.tsx
@@ -11,6 +11,49 @@ export function isAbsoluteHttpUrl(value: string): boolean {
return /^https?:\/\/\S+/i.test(value.trim());
}
+const OFFICIAL_PROVIDER_HOST_SUFFIXES = [
+ 'openai.com',
+ 'anthropic.com',
+ 'openrouter.ai',
+ 'deepseek.com',
+ 'googleapis.com',
+ 'google.com',
+ 'x.ai',
+ 'mistral.ai',
+ 'groq.com',
+ 'cerebras.ai',
+ 'amazonaws.com',
+ 'azure.com',
+] as const;
+
+function hostnameFromUrl(value: string | undefined): string | null {
+ if (!value) return null;
+ try {
+ return new URL(value).hostname.toLowerCase();
+ } catch {
+ return null;
+ }
+}
+
+function isOfficialProviderHost(hostname: string | null): boolean {
+ if (!hostname) return false;
+ return OFFICIAL_PROVIDER_HOST_SUFFIXES.some(
+ (suffix) => hostname === suffix || hostname.endsWith(`.${suffix}`),
+ );
+}
+
+export function shouldShowGatewayAllowlistHint(
+ errorCode: ErrorCode,
+ baseUrl: string,
+ attemptedUrl?: string,
+): boolean {
+ const normalised = String(errorCode).toUpperCase();
+ if (!['400', '401', '403', 'PARSE'].includes(normalised)) return false;
+ const hostname = hostnameFromUrl(attemptedUrl) ?? hostnameFromUrl(baseUrl);
+ if (!hostname || hostname === 'localhost' || hostname === '127.0.0.1') return false;
+ return !isOfficialProviderHost(hostname);
+}
+
export interface ConnectionDiagnosticPanelProps {
/** The error code returned by connection.test or generate */
errorCode: ErrorCode;
@@ -58,6 +101,7 @@ export function ConnectionDiagnosticPanel({
? fix.baseUrlTransform(baseUrl)
: undefined;
const canApplyFix = suggestedUrl !== undefined || fix?.externalUrl !== undefined;
+ const showGatewayAllowlistHint = shouldShowGatewayAllowlistHint(errorCode, baseUrl, attemptedUrl);
function handleApplyFix() {
if (suggestedUrl !== undefined) {
@@ -137,6 +181,16 @@ export function ConnectionDiagnosticPanel({
{t('diagnostics.fix.addV1')}: {suggestedUrl}
)}
+ {showGatewayAllowlistHint && (
+
+
+ {t('diagnostics.gatewayAllowlistHintTitle')}
+
+
+ {t('diagnostics.gatewayAllowlistHintBody')}
+
+
+ )}
{/* Actions */}
diff --git a/packages/i18n/src/locales/en.json b/packages/i18n/src/locales/en.json
index 0f4ed923..d86773e6 100644
--- a/packages/i18n/src/locales/en.json
+++ b/packages/i18n/src/locales/en.json
@@ -216,6 +216,8 @@
"apiKey": "API Key",
"apiKeyEditPlaceholder": "Leave empty to keep {{mask}}",
"defaultModel": "Default model",
+ "compatibilityHintTitle": "Compatibility note",
+ "compatibilityHintBody": "Some coding plans, relay services, or OpenAI-compatible gateways only allow specific clients such as Claude Code, openclaw, or Hermes. Even if the API looks compatible, Open CoDesign may still be blocked by an app allowlist.",
"switchToManual": "Enter manually",
"switchToDropdown": "Pick from list",
"discoveringModels": "Discovering models...",
@@ -824,6 +826,8 @@
"testAgain": "Test again",
"showLog": "Show full log",
"showLogFailed": "Failed to open logs folder",
+ "gatewayAllowlistHintTitle": "This endpoint may restrict which apps can connect",
+ "gatewayAllowlistHintBody": "Some coding plans and protocol-conversion gateways keep an app allowlist. Even when they advertise an OpenAI-compatible API, they may still reject Open CoDesign unless this app is explicitly allowed.",
"dismiss": "Dismiss",
"report": {
"title": "Report a bug",
diff --git a/packages/i18n/src/locales/pt-BR.json b/packages/i18n/src/locales/pt-BR.json
index 7eb0cae0..744236ae 100644
--- a/packages/i18n/src/locales/pt-BR.json
+++ b/packages/i18n/src/locales/pt-BR.json
@@ -213,6 +213,8 @@
"apiKey": "Chave de API",
"apiKeyEditPlaceholder": "Deixe vazio para manter {{mask}}",
"defaultModel": "Modelo padrão",
+ "compatibilityHintTitle": "Aviso de compatibilidade",
+ "compatibilityHintBody": "Alguns coding plans, relays e gateways compatíveis com OpenAI só permitem clientes específicos, como Claude Code, openclaw ou Hermes. Mesmo que a API pareça compatível, o Open CoDesign ainda pode ser bloqueado por uma lista de apps permitidos.",
"test": "Testar conexão",
"testOk": "OK — {{count}} modelos disponíveis",
"save": "Salvar e continuar",
@@ -806,6 +808,8 @@
"testAgain": "Testar novamente",
"showLog": "Mostrar log completo",
"showLogFailed": "Falha ao abrir a pasta de logs",
+ "gatewayAllowlistHintTitle": "Este endpoint pode restringir quais apps podem se conectar",
+ "gatewayAllowlistHintBody": "Alguns coding plans e gateways de conversão de protocolo mantêm uma lista de apps permitidos. Mesmo anunciando uma API compatível com OpenAI, eles ainda podem recusar o Open CoDesign se este app não estiver explicitamente liberado.",
"dismiss": "Dispensar",
"report": {
"title": "Reportar um bug",
diff --git a/packages/i18n/src/locales/zh-CN.json b/packages/i18n/src/locales/zh-CN.json
index a0bdf354..2ab06b4b 100644
--- a/packages/i18n/src/locales/zh-CN.json
+++ b/packages/i18n/src/locales/zh-CN.json
@@ -216,6 +216,8 @@
"apiKey": "API Key",
"apiKeyEditPlaceholder": "留空则保留 {{mask}}",
"defaultModel": "默认模型",
+ "compatibilityHintTitle": "\u517c\u5bb9\u6027\u63d0\u793a",
+ "compatibilityHintBody": "\u90e8\u5206 coding plan\uff0c\u534f\u8bae\u8f6c\u6362\u670d\u52a1\u6216 OpenAI \u517c\u5bb9\u7f51\u5173\u53ea\u5141\u8bb8\u7279\u5b9a\u5ba2\u6237\u7aef\uff08\u5982 Claude Code\uff0copenclaw\uff0cHermes\uff09\u8bbf\u95ee\u3002\u5373\u4f7f API \u770b\u8d77\u6765\u517c\u5bb9\uff0cOpen CoDesign \u4e5f\u53ef\u80fd\u56e0\u4e3a\u5e94\u7528\u767d\u540d\u5355\u800c\u65e0\u6cd5\u4f7f\u7528\u3002",
"switchToManual": "手动输入",
"switchToDropdown": "从列表选择",
"discoveringModels": "正在发现模型…",
@@ -820,6 +822,8 @@
"testAgain": "再次测试",
"showLog": "查看完整日志",
"showLogFailed": "无法打开日志文件夹",
+ "gatewayAllowlistHintTitle": "\u8fd9\u4e2a\u7aef\u70b9\u53ef\u80fd\u9650\u5236\u53ef\u63a5\u5165\u7684\u5e94\u7528",
+ "gatewayAllowlistHintBody": "\u90e8\u5206 coding plan \u548c\u534f\u8bae\u8f6c\u6362\u7f51\u5173\u4f1a\u914d\u7f6e\u5e94\u7528\u767d\u540d\u5355\u3002\u5b83\u4eec\u5373\u4f7f\u58f0\u79f0\u63d0\u4f9b OpenAI \u517c\u5bb9 API\uff0c\u4e5f\u53ef\u80fd\u5728\u6ca1\u6709\u663e\u5f0f\u5141\u8bb8 Open CoDesign \u7684\u60c5\u51b5\u4e0b\u62d2\u7edd\u8bf7\u6c42\u3002",
"dismiss": "关闭",
"report": {
"title": "上报问题",
From 5678f5f5b2bfc9aadc1525799401991d016922d1 Mon Sep 17 00:00:00 2001
From: Sun-sunshine06
Date: Thu, 23 Apr 2026 14:29:26 +0800
Subject: [PATCH 4/4] fix: address bot review feedback
---
.../src/components/AddCustomProviderModal.tsx | 4 +-
.../components/ConnectionDiagnosticPanel.tsx | 2 +-
packages/providers/src/index.test.ts | 43 -------------------
packages/providers/src/index.ts | 27 +-----------
4 files changed, 4 insertions(+), 72 deletions(-)
diff --git a/apps/desktop/src/renderer/src/components/AddCustomProviderModal.tsx b/apps/desktop/src/renderer/src/components/AddCustomProviderModal.tsx
index 4d1326c5..f706e907 100644
--- a/apps/desktop/src/renderer/src/components/AddCustomProviderModal.tsx
+++ b/apps/desktop/src/renderer/src/components/AddCustomProviderModal.tsx
@@ -327,9 +327,9 @@ export function AddCustomProviderModal({
disabled={lockEndpoint}
/>
{!lockEndpoint && (
-
+
-
+
{t('settings.providers.custom.compatibilityHintTitle')}
diff --git a/apps/desktop/src/renderer/src/components/ConnectionDiagnosticPanel.tsx b/apps/desktop/src/renderer/src/components/ConnectionDiagnosticPanel.tsx
index 44b6d8fe..e834d254 100644
--- a/apps/desktop/src/renderer/src/components/ConnectionDiagnosticPanel.tsx
+++ b/apps/desktop/src/renderer/src/components/ConnectionDiagnosticPanel.tsx
@@ -182,7 +182,7 @@ export function ConnectionDiagnosticPanel({
)}
{showGatewayAllowlistHint && (
-
+
{t('diagnostics.gatewayAllowlistHintTitle')}
diff --git a/packages/providers/src/index.test.ts b/packages/providers/src/index.test.ts
index dbf73877..e05e30ae 100644
--- a/packages/providers/src/index.test.ts
+++ b/packages/providers/src/index.test.ts
@@ -555,47 +555,4 @@ describe('inferReasoning', () => {
it('returns false when wire is undefined', () => {
expect(inferReasoning(undefined, 'gpt-4o', 'https://api.openai.com/v1')).toBe(false);
});
-
- it('returns true for third-party openai-chat with reasoning model ID (issue #188)', () => {
- // univibe/custom proxy with Claude 4 model
- expect(inferReasoning('openai-chat', 'claude-opus-4-6', 'https://api.univibe.cc/openai')).toBe(
- true,
- );
- expect(
- inferReasoning('openai-chat', 'claude-sonnet-4-6', 'https://api.univibe.cc/openai'),
- ).toBe(true);
- // OpenRouter-style paths on custom proxy
- expect(
- inferReasoning('openai-chat', 'anthropic/claude-opus-4-6', 'https://my-proxy.example/v1'),
- ).toBe(true);
- // OpenAI-style namespaced paths on custom proxy
- expect(inferReasoning('openai-chat', 'openai/o3-mini', 'https://my-proxy.example/v1')).toBe(
- true,
- );
- expect(inferReasoning('openai-chat', 'openai/gpt-5.1', 'https://my-proxy.example/v1')).toBe(
- true,
- );
- // o1 on custom proxy
- expect(inferReasoning('openai-chat', 'o1-mini', 'https://my-proxy.example/v1')).toBe(true);
- // qwen/qwq on custom proxy
- expect(
- inferReasoning('openai-chat', 'qwen/qwq-32b-preview', 'https://my-proxy.example/v1'),
- ).toBe(true);
- });
-
- it('returns false for third-party openai-chat with non-reasoning model ID', () => {
- expect(
- inferReasoning(
- 'openai-chat',
- 'qwen3.6-plus',
- 'https://dashscope.aliyuncs.com/compatible-mode/v1',
- ),
- ).toBe(false);
- expect(inferReasoning('openai-chat', 'deepseek-chat', 'https://api.deepseek.com/v1')).toBe(
- false,
- );
- expect(inferReasoning('openai-chat', 'glm-4.6v', 'https://open.bigmodel.cn/api/paas/v4')).toBe(
- false,
- );
- });
});
diff --git a/packages/providers/src/index.ts b/packages/providers/src/index.ts
index 29c1140b..940b103e 100644
--- a/packages/providers/src/index.ts
+++ b/packages/providers/src/index.ts
@@ -189,24 +189,6 @@ function isReasoningModelId(modelId: string): boolean {
return /^(o[134]|gpt-5)/i.test(modelId);
}
-/**
- * Matches reasoning-capable model IDs commonly proxied through OpenAI-compatible
- * gateways (OpenRouter, univibe, sub2api, etc). This pattern matches the same
- * set that OPENROUTER_REASONING_MODEL_RE uses for OpenRouter, but applies to
- * custom openai-chat wire endpoints as well.
- */
-const REASONING_MODEL_ID_PATTERN = new RegExp(
- [
- ':thinking$',
- '(^|/)claude-(?:opus|sonnet)-4',
- '^(?:openai/)?(?:o1|o3|o4|gpt-5)(?:[-.].*)?$',
- '^minimax/minimax-m\\d',
- '^deepseek/deepseek-r\\d',
- '^qwen/qwq',
- ].join('|'),
- 'i',
-);
-
export function inferReasoning(
wire: GenerateOptions['wire'],
modelId: string,
@@ -219,14 +201,7 @@ export function inferReasoning(
case 'openai-codex-responses':
return true;
case 'openai-chat':
- // For official OpenAI, check both base URL and model ID pattern
- if (isOpenAIOfficial(baseUrl)) {
- return isReasoningModelId(modelId);
- }
- // For third-party OpenAI-compatible gateways, heuristically match
- // common reasoning model IDs — many gateways still require the
- // reasoning flag to get extended thinking output.
- return REASONING_MODEL_ID_PATTERN.test(modelId);
+ return isOpenAIOfficial(baseUrl) && isReasoningModelId(modelId);
default:
return false;
}