You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by as...@apache.org on 2026/08/25 07:01:50 UTC
(maka) branch main updated: feat(desktop): show effective provider endpoints (#3639)
This is an automated email from the ASF dual-hosted git repository.
Astro-Han pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/maka.git
The following commit(s) were added to refs/heads/main by this push:
new 131eec2e6 feat(desktop): show effective provider endpoints (#3639)
131eec2e6 is described below
commit 131eec2e63d2de2f26bde89b99b315f087c0d1c5
Author: Hu Yang <47...@users.noreply.github.com>
AuthorDate: Tue Aug 25 15:01:45 2026 +0800
feat(desktop): show effective provider endpoints (#3639)
* feat(desktop): show effective provider endpoints
Generated-by: OpenAI Codex
* fix(desktop): address endpoint presentation review findings
- Mask every query value in displayed endpoints, not just known secret
key names: a relay may carry credentials under arbitrary keys.
- Gate the endpoint editor behind the masked-by-default PasswordInput
when the saved URL embeds userinfo or query credentials.
- Note model-level endpoint overrides under connection-level defaults,
and only then: a configured baseUrl wins over every override.
---
.../provider-endpoint-presentation.test.ts | 204 +++++++++++++++++++++
.../src/renderer/locales/settings-provider-copy.ts | 10 +-
.../settings/provider-connection-detail.tsx | 82 ++++++---
.../settings/provider-endpoint-presentation.ts | 156 ++++++++++++++++
.../renderer/styles/settings/provider-editor.css | 16 ++
.../stories/settings/provider-settings.stories.tsx | 46 ++++-
6 files changed, 483 insertions(+), 31 deletions(-)
diff --git a/apps/desktop/src/main/__tests__/provider-endpoint-presentation.test.ts b/apps/desktop/src/main/__tests__/provider-endpoint-presentation.test.ts
new file mode 100644
index 000000000..0db9c21c9
--- /dev/null
+++ b/apps/desktop/src/main/__tests__/provider-endpoint-presentation.test.ts
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import assert from 'node:assert/strict';
+import test from 'node:test';
+import { PROVIDER_DEFAULTS } from '@maka/core/llm-connections';
+import {
+ endpointCarriesCredentials,
+ providerEndpointPresentation,
+} from '../../renderer/settings/provider-endpoint-presentation.js';
+
+// A 40-char hex-shaped run, built rather than written: long enough to trip
+// the display redactor's long-opaque-token rule wherever it is left alone.
+const longOpaqueToken = 'ab01'.repeat(10);
+
+test('fixed Alibaba access paths expose their distinct effective endpoints read-only', () => {
+ const api = providerEndpointPresentation({ providerType: 'alibaba' });
+ const tokenPlanChina = providerEndpointPresentation({ providerType: 'alibaba-token-plan-cn' });
+
+ assert.deepEqual(api, {
+ value: PROVIDER_DEFAULTS.alibaba.baseUrl,
+ editable: false,
+ emptyState: 'missing',
+ });
+ assert.deepEqual(tokenPlanChina, {
+ value: PROVIDER_DEFAULTS['alibaba-token-plan-cn'].baseUrl,
+ editable: false,
+ emptyState: 'missing',
+ });
+ assert.match(api.value!, /^https:\/\//);
+ assert.match(tokenPlanChina.value!, /^https:\/\//);
+ assert.notEqual(api.value, tokenPlanChina.value);
+});
+
+test('a persisted override is the displayed effective endpoint', () => {
+ assert.deepEqual(
+ providerEndpointPresentation({
+ providerType: 'alibaba',
+ baseUrl: ' https://relay.example.com/alibaba/v1/ ',
+ }),
+ {
+ value: 'https://relay.example.com/alibaba/v1/',
+ editable: false,
+ emptyState: 'missing',
+ },
+ );
+});
+
+test('displaying a custom endpoint masks userinfo and every query value without hiding its route', () => {
+ assert.deepEqual(
+ providerEndpointPresentation({
+ providerType: 'openai-compatible',
+ baseUrl:
+ `https://relay-user:relay-password@relay.example.com/v1?api-version=2026-08-01&api_key=${longOpaqueToken}`,
+ }),
+ {
+ value:
+ 'https://<redacted>@relay.example.com/v1?api-version=<redacted>&api_key=<redacted>',
+ editable: true,
+ emptyState: 'missing',
+ },
+ );
+});
+
+test('query values are masked under arbitrary key names, not just known ones', () => {
+ assert.deepEqual(
+ providerEndpointPresentation({
+ providerType: 'openai-compatible',
+ baseUrl: `https://relay.example.com/v1?key=${longOpaqueToken}`,
+ }),
+ {
+ value: 'https://relay.example.com/v1?key=<redacted>',
+ editable: true,
+ emptyState: 'missing',
+ },
+ );
+ assert.deepEqual(
+ providerEndpointPresentation({
+ providerType: 'openai-compatible',
+ baseUrl: `https://relay.example.com/v1?client_secret=${longOpaqueToken}`,
+ }),
+ {
+ value: 'https://relay.example.com/v1?client_secret=<redacted>',
+ editable: true,
+ emptyState: 'missing',
+ },
+ );
+});
+
+test('custom relays and local runtimes retain endpoint editing', () => {
+ assert.deepEqual(
+ providerEndpointPresentation({
+ providerType: 'openai-compatible',
+ baseUrl: 'https://relay.example.com/v1',
+ }),
+ {
+ value: 'https://relay.example.com/v1',
+ editable: true,
+ emptyState: 'missing',
+ },
+ );
+ assert.deepEqual(
+ providerEndpointPresentation({ providerType: 'ollama' }),
+ {
+ value: PROVIDER_DEFAULTS.ollama.baseUrl,
+ editable: true,
+ emptyState: 'missing',
+ },
+ );
+});
+
+test('derived and OAuth endpoints remain visible but read-only', () => {
+ assert.deepEqual(
+ providerEndpointPresentation({
+ providerType: 'cloudflare-workers-ai',
+ baseUrl: 'https://api.cloudflare.com/client/v4/accounts/example/ai/v1',
+ }),
+ {
+ value: 'https://api.cloudflare.com/client/v4/accounts/example/ai/v1',
+ editable: false,
+ emptyState: 'missing',
+ },
+ );
+ assert.deepEqual(
+ providerEndpointPresentation({ providerType: 'openai-codex' }),
+ {
+ value: PROVIDER_DEFAULTS['openai-codex'].baseUrl,
+ editable: false,
+ emptyState: 'managed',
+ },
+ );
+});
+
+test('an absent custom endpoint remains visible as a missing editable value', () => {
+ assert.deepEqual(
+ providerEndpointPresentation({ providerType: 'openai-compatible' }),
+ { value: null, editable: true, emptyState: 'missing' },
+ );
+});
+
+test('providers with model-level endpoint overrides say so when showing the default', () => {
+ // ZenMux routes Anthropic-family models through .../api/anthropic/v1 and
+ // Cohere routes north-mini-code-1-0 through the compatibility endpoint, so
+ // the connection-level default is not the whole truth for every model.
+ assert.deepEqual(
+ providerEndpointPresentation({ providerType: 'zenmux' }),
+ {
+ value: PROVIDER_DEFAULTS.zenmux.baseUrl,
+ editable: false,
+ emptyState: 'missing',
+ modelOverrides: true,
+ },
+ );
+ assert.deepEqual(
+ providerEndpointPresentation({ providerType: 'cohere' }),
+ {
+ value: PROVIDER_DEFAULTS.cohere.baseUrl,
+ editable: false,
+ emptyState: 'missing',
+ modelOverrides: true,
+ },
+ );
+});
+
+test('a configured endpoint wins over model overrides, so no caveat applies', () => {
+ // resolveModelRuntime uses a configured baseUrl for every model; the row is
+ // then the exact truth and must not carry the override note.
+ assert.deepEqual(
+ providerEndpointPresentation({
+ providerType: 'zenmux',
+ baseUrl: 'https://relay.example.com/v1',
+ }),
+ {
+ value: 'https://relay.example.com/v1',
+ editable: false,
+ emptyState: 'missing',
+ },
+ );
+});
+
+test('endpointCarriesCredentials gates userinfo and query-bearing endpoints', () => {
+ assert.equal(endpointCarriesCredentials('https://relay-user:relay-password@relay.example.com/v1'), true);
+ assert.equal(endpointCarriesCredentials('https://relay.example.com/v1?api-version=2026-08-01'), true);
+ assert.equal(endpointCarriesCredentials('https://relay.example.com/v1'), false);
+ assert.equal(endpointCarriesCredentials(''), false);
+ assert.equal(endpointCarriesCredentials(undefined), false);
+ assert.equal(endpointCarriesCredentials('not a url'), false);
+});
diff --git a/apps/desktop/src/renderer/locales/settings-provider-copy.ts b/apps/desktop/src/renderer/locales/settings-provider-copy.ts
index cd36f4e91..20a7c71ce 100644
--- a/apps/desktop/src/renderer/locales/settings-provider-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-provider-copy.ts
@@ -120,7 +120,10 @@ const zhCopy = {
// Row affordances (settings-sidebar 的 InfoRow / ExpandableRow 语言):一行
// 只报状态,改的时候才展开成输入框。
change: '更换', set: '设置', edit: '编辑', save: '保存',
- endpointDefault: '服务商默认地址',
+ endpointManaged: '由账号登录或服务商管理',
+ endpointMissing: '尚未配置服务地址',
+ endpointModelOverridesNote: '部分模型会通过其他服务端点发送请求',
+ endpointCredentialsMasked: '已保存的地址内嵌凭据,编辑时默认隐藏',
modelManagement: '模型',
disconnectAndDelete: '退出账号并删除',
copilotImportFailed: '导入 GitHub Copilot 登录失败', copilotLoggedIn: 'GitHub Copilot 已登录', copilotWaiting: '等待兼容 GitHub 凭据',
@@ -267,7 +270,10 @@ const enCopy: ProviderSettingsCopy = {
...enCapabilitiesCopy,
capabilitiesHelp: 'Declares thinking levels, vision, and context window per enabled model; applies on save.',
change: 'Change', set: 'Set', edit: 'Edit', save: 'Save',
- endpointDefault: 'The provider default',
+ endpointManaged: 'Managed by account sign-in or the provider',
+ endpointMissing: 'No service URL configured',
+ endpointModelOverridesNote: 'Some models send requests through a different endpoint',
+ endpointCredentialsMasked: 'The saved URL embeds credentials and stays masked while editing',
modelManagement: 'Models',
disconnectAndDelete: 'Sign out and delete',
copilotImportFailed: 'Failed to import GitHub Copilot sign-in', copilotLoggedIn: 'GitHub Copilot signed in', copilotWaiting: 'Waiting for compatible GitHub credentials',
diff --git a/apps/desktop/src/renderer/settings/provider-connection-detail.tsx b/apps/desktop/src/renderer/settings/provider-connection-detail.tsx
index 5b3ffa1b0..a004f541f 100644
--- a/apps/desktop/src/renderer/settings/provider-connection-detail.tsx
+++ b/apps/desktop/src/renderer/settings/provider-connection-detail.tsx
@@ -51,6 +51,7 @@ import {
} from '@maka/ui';
import { PasswordInput } from './password-input';
import { SettingsExpandableRow } from './settings-expandable-row';
+import { SettingsRow } from './settings-section';
import { getProviderSettingsCopy } from '../locales/settings-provider-copy';
import { providerDisplay } from './provider-display';
import { AddModelDialog } from './provider-add-model-dialog';
@@ -82,6 +83,7 @@ import {
type RequestHeaderDraft,
} from './request-customization-editor';
import { bulkThinkingLevelStates } from './relay-thinking-bulk';
+import { endpointCarriesCredentials, providerEndpointPresentation } from './provider-endpoint-presentation';
export function ConnectionDetail(props: ConnectionDetailProps) {
const defaults = PROVIDER_DEFAULTS[props.connection.providerType];
@@ -343,15 +345,26 @@ function ConnectionDetailInner(props: ConnectionDetailProps) {
if (mounted.current) setRequestCustomizationBusy(false);
}
}
- // Most of the 60 providers publish a fixed endpoint; editing it there can
- // only break the connection, and a proxy belongs in 设置 · 通用 · 网络, not in
- // a per-connection URL. So the row exists only where the address is genuinely
- // the user's: a service with no published endpoint (the *-compatible ones), or
- // a local runtime whose port is a convention rather than a fact. A derived
- // endpoint (Cloudflare builds one from the account id) is nobody's to type.
- const showsEndpoint = !needsOAuth
- && !defaults.baseUrlTemplate
- && (!defaults.baseUrl || defaults.category === 'local');
+ // Every known connection reports where requests go. Editability remains the
+ // narrower authority: built-in and derived endpoints are visible but fixed,
+ // while custom relays and local runtimes keep their existing editor.
+ const endpoint = providerEndpointPresentation(connection);
+ const endpointValue = endpoint.value
+ ? <code className="settingsReadOnlyValue providerEndpointValue" data-mono="true">{endpoint.value}</code>
+ : endpoint.emptyState === 'managed'
+ ? copy.endpointManaged
+ : copy.endpointMissing;
+ // Model-level endpoint overrides mean the connection-level base is not the
+ // whole truth for every model; say so under the value rather than implying
+ // one address serves all models.
+ const endpointNote = endpoint.modelOverrides
+ ? <span className="providerEndpointNote">{copy.endpointModelOverridesNote}</span>
+ : null;
+ const endpointDisplay = endpointNote ? <>{endpointValue}{endpointNote}</> : endpointValue;
+ // A credential-bearing saved endpoint must not prefill a plain text input:
+ // the editor falls back to the masked-by-default PasswordInput, which the
+ // user can deliberately reveal.
+ const endpointHasCredentials = endpointCarriesCredentials(savedBaseUrl);
return (
/* Three sections, each a heading with a sentence beside its controls, one
@@ -457,8 +470,7 @@ function ConnectionDetailInner(props: ConnectionDetailProps) {
</SettingsExpandableRow>
</VStack>
)}
- {(supportsApiKey || showsEndpoint) && (
- <VStack gap={0}>
+ <VStack gap={0}>
<Divider />
{supportsApiKey && (
<>
@@ -497,11 +509,11 @@ function ConnectionDetailInner(props: ConnectionDetailProps) {
<Divider />
</>
)}
- {showsEndpoint && (
+ {endpoint.editable ? (
<>
<SettingsExpandableRow
label={copy.endpoint}
- value={savedBaseUrl || copy.endpointDefault}
+ value={endpointDisplay}
actionLabel={copy.edit}
actionAriaLabel={`${copy.edit}: ${copy.endpoint}`}
isEditing={editingRow === 'endpoint'}
@@ -513,25 +525,41 @@ function ConnectionDetailInner(props: ConnectionDetailProps) {
onCancel={() => { setBaseUrl(savedBaseUrl); setEditingRow(null); }}
onSave={async () => { if (await save('endpoint')) setEditingRow(null); }}
>
- <TextInput
- label={copy.endpoint}
- isLabelHidden
- value={baseUrl}
- onChange={setBaseUrl}
- placeholder={defaults.baseUrl}
- isDisabled={allActionsBusy}
- />
+ {endpointHasCredentials ? (
+ <PasswordInput
+ value={baseUrl}
+ onChange={setBaseUrl}
+ placeholder={defaults.baseUrl}
+ label={copy.endpoint}
+ isLabelHidden
+ description={copy.endpointCredentialsMasked}
+ isDisabled={allActionsBusy}
+ />
+ ) : (
+ <TextInput
+ label={copy.endpoint}
+ isLabelHidden
+ value={baseUrl}
+ onChange={setBaseUrl}
+ placeholder={defaults.baseUrl}
+ isDisabled={allActionsBusy}
+ />
+ )}
</SettingsExpandableRow>
<Divider />
</>
+ ) : (
+ <>
+ <SettingsRow
+ label={copy.endpoint}
+ description={endpointDisplay}
+ align="start"
+ />
+ <Divider />
+ </>
)}
- </VStack>
- )}
+ </VStack>
</DetailSection>
- {/* The rows draw the closing rule themselves; without them the section
- still needs one. Two rules with a gap between them read as an empty
- row, so only ever one. */}
- {!supportsApiKey && !showsEndpoint && !retired && <Divider />}
{/* Everything below writes to the connection, and a retired one accepts
no writes: the catalog refuses a model or request-body change, and the
credential vault refuses a request header. Rendering the editors would
diff --git a/apps/desktop/src/renderer/settings/provider-endpoint-presentation.ts b/apps/desktop/src/renderer/settings/provider-endpoint-presentation.ts
new file mode 100644
index 000000000..d01d86281
--- /dev/null
+++ b/apps/desktop/src/renderer/settings/provider-endpoint-presentation.ts
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ effectiveBaseUrl,
+ PROVIDER_DEFAULTS,
+ type LlmConnection,
+} from '@maka/core/llm-connections';
+import {
+ lookupModelProviderOverride,
+ modelMetadataIdsForProvider,
+} from '@maka/core/model-metadata';
+import { redactSecrets } from '@maka/core/display-redaction';
+
+export interface ProviderEndpointPresentation {
+ /** The effective base endpoint, with any display-facing credentials masked. */
+ value: string | null;
+ /** Whether the connection detail page should offer an endpoint editor. */
+ editable: boolean;
+ /** Explains an absent concrete value without making the row disappear. */
+ emptyState: 'managed' | 'missing';
+ /**
+ * The displayed value is the connection-level base endpoint, but some models
+ * route through model-level endpoint overrides before a request, so an
+ * individual model may use a different address. Present only when the
+ * connection itself configures no baseUrl — a configured baseUrl wins over
+ * every model-level override at runtime, so then the row is the exact truth.
+ */
+ modelOverrides?: true;
+}
+
+/**
+ * Resolve the endpoint fact shown on a provider connection detail page.
+ *
+ * Visibility and editability are deliberately separate. Built-in providers
+ * still own their fixed URL, but the user needs to see it to distinguish
+ * similarly named access paths and regions. Custom relays and local runtimes
+ * keep the existing editor because their address genuinely belongs to the
+ * connection. Derived endpoints (for example Cloudflare account URLs) are
+ * concrete once persisted, but are never hand-edited here.
+ */
+export function providerEndpointPresentation(
+ connection: {
+ providerType: LlmConnection['providerType'];
+ baseUrl?: string;
+ },
+): ProviderEndpointPresentation {
+ const defaults = PROVIDER_DEFAULTS[connection.providerType];
+ const effective = effectiveBaseUrl(connection).trim();
+ const value = endpointForDisplay(effective);
+ const editable = defaults.authKind !== 'oauth_token'
+ && !defaults.baseUrlTemplate
+ && (!defaults.baseUrl || defaults.category === 'local');
+
+ return {
+ value: value || null,
+ editable,
+ emptyState: defaults.authKind === 'oauth_token' ? 'managed' : 'missing',
+ ...(providerRoutesModelsElsewhere(connection) ? { modelOverrides: true as const } : {}),
+ };
+}
+
+/** The override table is immutable generated data, so a per-provider verdict is stable. */
+const modelOverrideRouteCache = new Map<LlmConnection['providerType'], boolean>();
+
+/**
+ * Whether the runtime may route some of this connection's models through a
+ * different endpoint than the connection-level base. Mirrors
+ * `resolveModelRuntime`: a configured baseUrl always wins, and only without
+ * one can a model-level override replace the provider default.
+ */
+function providerRoutesModelsElsewhere(
+ connection: { providerType: LlmConnection['providerType']; baseUrl?: string },
+): boolean {
+ if (connection.baseUrl?.trim()) return false;
+ const defaultBaseUrl = PROVIDER_DEFAULTS[connection.providerType]?.baseUrl;
+ if (!defaultBaseUrl) return false;
+ const cached = modelOverrideRouteCache.get(connection.providerType);
+ if (cached !== undefined) return cached;
+ let routes = false;
+ for (const modelId of modelMetadataIdsForProvider(connection.providerType)) {
+ const api = lookupModelProviderOverride(connection.providerType, modelId)?.api;
+ if (api && api !== defaultBaseUrl) {
+ routes = true;
+ break;
+ }
+ }
+ modelOverrideRouteCache.set(connection.providerType, routes);
+ return routes;
+}
+
+/**
+ * Whether a persisted endpoint embeds credentials: URL userinfo, or any query
+ * parameter at all — a relay may name a credential parameter arbitrarily, so
+ * the mere presence of a query string makes the value credential-shaped for
+ * display. The endpoint editor uses this to gate its input behind a masked
+ * state instead of prefilling the raw value into a plain text field.
+ */
+export function endpointCarriesCredentials(value: string | null | undefined): boolean {
+ if (!value) return false;
+ try {
+ const parsed = new URL(value.trim());
+ return Boolean(parsed.username || parsed.password) || parsed.search !== '';
+ } catch {
+ return false;
+ }
+}
+
+function endpointForDisplay(value: string): string {
+ if (!value) return value;
+ // Base URL secrets hide in three places: userinfo, query values under
+ // arbitrary key names, and (rarely) path-embedded tokens. URL parsing
+ // redacts the first two deterministically; the shared redactor sweeps
+ // whatever remains.
+ try {
+ const parsed = new URL(value);
+ const carriesUserinfo = Boolean(parsed.username || parsed.password);
+ parsed.username = '';
+ parsed.password = '';
+ // A base URL's query string has no display-safe semantics: a relay may put
+ // a credential under any key name (`key`, `client_secret`, ...), so mask
+ // every value and keep the key names to preserve the endpoint's shape.
+ // Rebuild by hand — URLSearchParams would percent-encode `<redacted>`.
+ const queryKeys = [...parsed.searchParams.keys()];
+ if (queryKeys.length > 0) parsed.search = '';
+ // Keep one marker so the user can still tell that the saved endpoint
+ // carries embedded credentials.
+ let display = carriesUserinfo
+ ? parsed.href.replace(`${parsed.protocol}//`, `${parsed.protocol}//<redacted>@`)
+ : parsed.href;
+ if (queryKeys.length > 0) {
+ display += `?${queryKeys.map((key) => `${key}=<redacted>`).join('&')}`;
+ }
+ return redactSecrets(display);
+ } catch {
+ // Persistence already validates provider base URLs. A legacy malformed
+ // value still gets best-effort masking rather than disappearing.
+ return redactSecrets(value);
+ }
+}
diff --git a/apps/desktop/src/renderer/styles/settings/provider-editor.css b/apps/desktop/src/renderer/styles/settings/provider-editor.css
index 1a04c0f55..1e3fe1653 100644
--- a/apps/desktop/src/renderer/styles/settings/provider-editor.css
+++ b/apps/desktop/src/renderer/styles/settings/provider-editor.css
@@ -28,6 +28,22 @@
/* .settingsRow label/value typography moved to rows.css
(PR-SETTINGS-ROWS-CONVERGENCE-0). */
+/* Endpoint values are machine text, but unlike opaque filesystem paths they
+ carry useful URL boundaries. Prefer a slash/punctuation break before the
+ fallback anywhere-wrap so a long address does not split its hostname just
+ because the first line still had room for two more characters. */
+.settingsReadOnlyValue.providerEndpointValue[data-mono="true"] {
+ word-break: normal;
+}
+
+/* The model-override note is a second sentence under the endpoint's machine
+ text: block layout keeps it off the URL line, muted color keeps the URL
+ the primary fact. */
+.providerEndpointNote {
+ display: block;
+ color: var(--muted-foreground);
+}
+
@media (max-width: 980px) {
.app {
grid-template-columns: 1fr;
diff --git a/apps/desktop/stories/settings/provider-settings.stories.tsx b/apps/desktop/stories/settings/provider-settings.stories.tsx
index ce688f99b..3c0835f15 100644
--- a/apps/desktop/stories/settings/provider-settings.stories.tsx
+++ b/apps/desktop/stories/settings/provider-settings.stories.tsx
@@ -50,6 +50,7 @@ export default meta;
type Story = StoryObj<typeof meta>;
type AutoOpenTarget =
| 'detail'
+ | 'detail-alibaba'
| 'detail-static'
| 'detail-relay'
| 'add'
@@ -127,6 +128,21 @@ const configuredConnections = [
}),
];
+const alibabaTokenPlanConnections = [
+ makeConnection({
+ slug: 'alibaba-token-plan-cn',
+ name: 'Alibaba Token Plan(团队版)',
+ providerType: 'alibaba-token-plan-cn',
+ defaultModel: 'qwen3.8-max',
+ lastTestStatus: 'verified',
+ models: [
+ { id: 'qwen3.8-max', displayName: 'Qwen3.8 Max' },
+ { id: 'qwen3.7-max', displayName: 'Qwen3.7 Max' },
+ ],
+ modelSource: 'fetched',
+ }),
+];
+
// A provider whose key cannot call a model-list endpoint: refresh replays the
// array this build shipped, so 添加模型 replaces 更新模型目录 as the only way the
// catalog can grow. `deepseek-v4-pro-beta` is a model added that way — absent
@@ -426,11 +442,22 @@ function reachCatalog(root: HTMLElement): HTMLElement | null {
}
function clickAutoOpenTarget(root: HTMLElement, target: AutoOpenTarget): boolean {
- if (target === 'detail' || target === 'detail-static' || target === 'detail-relay') {
+ if (
+ target === 'detail'
+ || target === 'detail-alibaba'
+ || target === 'detail-static'
+ || target === 'detail-relay'
+ ) {
// ListItem's clickable surface is an invisible button inside the row, so
// the row is located by its slug hook and the button taken from within it.
const slug =
- target === 'detail' ? 'zai-live' : target === 'detail-static' ? 'ark-plan' : 'relay-house';
+ target === 'detail'
+ ? 'zai-live'
+ : target === 'detail-alibaba'
+ ? 'alibaba-token-plan-cn'
+ : target === 'detail-static'
+ ? 'ark-plan'
+ : 'relay-house';
const row = root.querySelector<HTMLElement>(`[data-connection-slug="${slug}"]`);
const detailButton = row?.querySelector('button') ?? null;
detailButton?.click();
@@ -496,6 +523,21 @@ export const ConnectionDetailPage: Story = {
),
};
+// Fixed endpoints are inspectable but not editable. Alibaba is the high-signal
+// case because several catalog entries share one brand while routing to
+// different products and regions (#3636).
+export const AlibabaConnectionDetailPage: Story = {
+ render: () => (
+ <ProviderStory
+ bridge={createBridge({
+ connections: alibabaTokenPlanConnections,
+ defaultSlug: 'alibaba-token-plan-cn',
+ })}
+ autoOpen="detail-alibaba"
+ />
+ ),
+};
+
// Real path: 设置 → 模型 → click a connection whose provider has no model-list
// endpoint — 添加模型 stands where 更新模型目录 would, and the capability section
// lists the models Maka's bundled metadata cannot describe.
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org