@ai-wendao/ant 0.4.11 → 0.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client.js CHANGED
@@ -6,6 +6,7 @@ window.__ModuleLoader__.load({
6
6
  const { createElement, useEffect, useRef, useState } = require('react')
7
7
  const h = createElement
8
8
  const SERVICE_URL_STORAGE_KEY = 'ant-team-service-url'
9
+ const PROVIDER_ROUTES_STORAGE_KEY = 'ant-team-provider-routes'
9
10
  const DEVICE_ID_STORAGE_KEY = 'ant-team-device-id'
10
11
  const DEFAULT_MODEL_INITIALIZED_KEY = 'ant-team-default-model-initialized'
11
12
  const ANT_BUNDLE_PACKAGE_NAME = '@ai-wendao/ant'
@@ -1989,9 +1990,12 @@ window.__ModuleLoader__.load({
1989
1990
  return status
1990
1991
  }
1991
1992
 
1992
- const modelProvider = (model) => model.protocol === 'anthropic' ? 'ant-team-anthropic'
1993
- : model.openai_api_mode === 'responses' ? 'ant-team-openai-responses' : 'ant-team-openai-chat-completions'
1994
- const modelSelectionKey = (model) => JSON.stringify({ protocol: model.protocol, model_id: model.model_id })
1993
+ const modelProvider = (model) => model.protocol === 'anthropic'
1994
+ ? model.provider_id === 'anthropic-default' || !model.provider_id ? 'ant-team-anthropic' : `ant-team-anthropic-${model.provider_id}`
1995
+ : model.provider_id === 'openai-default' || !model.provider_id
1996
+ ? model.openai_api_mode === 'responses' ? 'ant-team-openai-responses' : 'ant-team-openai-chat-completions'
1997
+ : `ant-team-openai-${model.provider_id}`
1998
+ const modelSelectionKey = (model) => JSON.stringify({ provider_id: model.provider_id || '', protocol: model.protocol, model_id: model.model_id })
1995
1999
  const defaultPreferenceKey = (rawServiceUrl) => `${DEFAULT_MODEL_INITIALIZED_KEY}:${normalizeServiceUrl(rawServiceUrl)}:${savedDeviceId() || ''}`
1996
2000
  const readDefaultPreference = (rawServiceUrl) => {
1997
2001
  try { return JSON.parse(localStorage.getItem(defaultPreferenceKey(rawServiceUrl)) || 'null') } catch { return null }
@@ -2003,9 +2007,9 @@ window.__ModuleLoader__.load({
2003
2007
  if (!selection) return null
2004
2008
  const exact = models.find((model) => model.model_id === selection.model && modelProvider(model) === selection.provider)
2005
2009
  if (exact) return exact
2006
- const legacyProtocol = selection.provider === 'ant-team-openai' || selection.provider?.startsWith('ant-team-openai-')
2007
- ? 'openai' : selection.provider === 'ant-team-anthropic' ? 'anthropic' : null
2008
- return legacyProtocol ? models.find((model) => model.protocol === legacyProtocol && model.model_id === selection.model) : null
2010
+ const legacyProvider = ['ant-team-openai', 'ant-team-openai-chat-completions', 'ant-team-openai-responses'].includes(selection.provider)
2011
+ ? 'openai-default' : selection.provider === 'ant-team-anthropic' ? 'anthropic-default' : null
2012
+ return legacyProvider ? models.find((model) => model.provider_id === legacyProvider && model.model_id === selection.model) : null
2009
2013
  }
2010
2014
  const readDshDefaultSelection = async () => {
2011
2015
  if (typeof remote?.settings?.describe !== 'function') throw new Error('当前 DSH 未提供全局默认模型读取接口。')
@@ -2051,34 +2055,69 @@ window.__ModuleLoader__.load({
2051
2055
  const normalized = normalizeServiceUrl(rawServiceUrl)
2052
2056
  if (!normalized || !Array.isArray(models)) throw new Error('ANT 返回的团队模型目录格式无效。')
2053
2057
  if (typeof remote?.settings?.mutate !== 'function') throw new Error('当前 DSH 版本未提供模型设置接口。')
2054
- const groups = [
2055
- { route: 'ant-team-openai-chat-completions', protocol: 'openai', mode: 'chat_completions', api: 'openai-completions' },
2056
- { route: 'ant-team-openai-responses', protocol: 'openai', mode: 'responses', api: 'openai-responses' },
2057
- { route: 'ant-team-anthropic', protocol: 'anthropic', api: 'anthropic-messages' },
2058
- ]
2059
- const ops = [{ op: 'unset', path: ['providers', 'ant-team-openai'] }]
2060
- for (const group of groups) {
2061
- const protocolModels = models.filter((model) => model.protocol === group.protocol
2062
- && (group.protocol !== 'openai' || (model.openai_api_mode || 'chat_completions') === group.mode))
2063
- const route = group.route
2064
- if (protocolModels.length === 0) {
2065
- ops.push({ op: 'unset', path: ['providers', route] })
2066
- continue
2058
+ const grouped = new Map()
2059
+ for (const model of models) {
2060
+ const protocol = model.protocol
2061
+ if (!['openai', 'anthropic'].includes(protocol)) continue
2062
+ const providerId = model.provider_id || (protocol === 'openai' ? 'openai-default' : 'anthropic-default')
2063
+ const key = `${providerId}\u0000${protocol}`
2064
+ let provider = grouped.get(key)
2065
+ if (!provider) {
2066
+ provider = {
2067
+ providerId,
2068
+ providerName: model.provider_name || (protocol === 'openai'
2069
+ ? `ANT 团队 · OpenAI · ${model.openai_api_mode === 'responses' ? 'Responses' : 'Chat Completions'}`
2070
+ : 'ANT 团队 · Anthropic'),
2071
+ protocol,
2072
+ apiMode: model.openai_api_mode === 'responses' ? 'responses' : 'chat_completions',
2073
+ legacyEndpoint: typeof model.provider_id !== 'string' || !model.provider_id,
2074
+ models: [],
2075
+ }
2076
+ grouped.set(key, provider)
2067
2077
  }
2078
+ provider.models.push(model)
2079
+ }
2080
+ const providers = [...grouped.values()]
2081
+ const routeForProvider = (provider) => provider.providerId === 'openai-default'
2082
+ ? provider.apiMode === 'responses' ? 'ant-team-openai-responses' : 'ant-team-openai-chat-completions'
2083
+ : provider.providerId === 'anthropic-default'
2084
+ ? 'ant-team-anthropic' : `ant-team-${provider.protocol}-${provider.providerId}`
2085
+ const routes = providers.map(routeForProvider)
2086
+ const ops = []
2087
+ let previousRoutes = []
2088
+ try {
2089
+ const parsed = JSON.parse(localStorage.getItem(PROVIDER_ROUTES_STORAGE_KEY) || '[]')
2090
+ if (Array.isArray(parsed)) previousRoutes = parsed.filter((route) => typeof route === 'string')
2091
+ } catch {}
2092
+ const knownRoutes = new Set([...previousRoutes, 'ant-team-openai', 'ant-team-openai-chat-completions',
2093
+ 'ant-team-openai-responses', 'ant-team-anthropic'])
2094
+ for (const route of knownRoutes) {
2095
+ if (!routes.includes(route)) ops.push({ op: 'unset', path: ['providers', route] })
2096
+ }
2097
+ for (const provider of providers) {
2098
+ const route = routeForProvider(provider)
2099
+ const api = provider.protocol === 'openai'
2100
+ ? provider.apiMode === 'responses' ? 'openai-responses' : 'openai-completions'
2101
+ : 'anthropic-messages'
2102
+ const baseURL = provider.legacyEndpoint
2103
+ ? new URL(provider.protocol === 'openai' ? '/model-gateway/openai/v1' : '/model-gateway/anthropic', normalized).toString()
2104
+ : provider.protocol === 'openai'
2105
+ ? new URL(`/model-gateway/openai/providers/${encodeURIComponent(provider.providerId)}/v1`, normalized).toString()
2106
+ : new URL(`/model-gateway/anthropic/providers/${encodeURIComponent(provider.providerId)}`, normalized).toString()
2068
2107
  ops.push({ op: 'set', path: ['providers', route], value: {
2069
- displayName: group.protocol === 'anthropic' ? 'ANT 团队 · Anthropic' : `ANT 团队 · OpenAI · ${group.mode === 'responses' ? 'Responses' : 'Chat Completions'}`,
2108
+ displayName: provider.providerName,
2070
2109
  apiKeyEnv: 'ANT_MODEL_API_KEY',
2071
- api: group.api,
2072
- baseURL: group.protocol === 'openai'
2073
- ? new URL('/model-gateway/openai/v1', normalized).toString()
2074
- : new URL('/model-gateway/anthropic', normalized).toString(),
2075
- models: protocolModels.map((model) => ({ id: model.model_id, name: model.display_name })),
2110
+ api,
2111
+ baseURL,
2112
+ models: provider.models.map((model) => ({ id: model.model_id, name: model.display_name })),
2076
2113
  } })
2077
2114
  }
2078
2115
  const settingsWrite = await remote.settings.mutate('llm-pi-ai', ops, undefined)
2079
2116
  if (!settingsWrite.ok) throw new Error(settingsWrite.error?.message || 'DSH 无法更新团队模型设置。')
2080
- const defaultMatch = rawDefaultModel && models.find((model) => model.protocol === rawDefaultModel.protocol
2081
- && model.model_id === rawDefaultModel.model_id)
2117
+ try { localStorage.setItem(PROVIDER_ROUTES_STORAGE_KEY, JSON.stringify(routes)) } catch {}
2118
+ const defaultMatch = rawDefaultModel && models.find((model) =>
2119
+ (rawDefaultModel.provider_id ? model.provider_id === rawDefaultModel.provider_id : model.protocol === rawDefaultModel.protocol)
2120
+ && model.protocol === rawDefaultModel.protocol && model.model_id === rawDefaultModel.model_id)
2082
2121
  const serverDefault = defaultMatch || null
2083
2122
  const initialDefault = defaultMatch || (rawDefaultModel == null ? models[0] : null)
2084
2123
  setServerDefaultModel(serverDefault)
@@ -2138,7 +2177,9 @@ window.__ModuleLoader__.load({
2138
2177
  setModelMessage('正在从 ANT 刷新获准模型…')
2139
2178
  try {
2140
2179
  await requireCompatibleService(serviceUrl, true)
2141
- const response = await fetch('/api/ant-teams/models', { method: 'POST' })
2180
+ const response = await fetch('/api/ant-teams/models', {
2181
+ method: 'POST', headers: { 'x-ant-model-provider-config': '1' },
2182
+ })
2142
2183
  const result = await readLocalJsonResponse(response)
2143
2184
  if (!response.ok) {
2144
2185
  if (response.status === 401) setNeedsReconnect(true)
@@ -2327,13 +2368,26 @@ window.__ModuleLoader__.load({
2327
2368
  ], undefined)
2328
2369
  if (!reset.ok) throw new Error(reset.error?.message || 'DSH 全局默认模型未能恢复。')
2329
2370
  }
2330
- const settingsResult = await remote.settings.mutate('llm-pi-ai', [
2331
- { op: 'unset', path: ['providers', 'ant-team-openai-chat-completions'] },
2332
- { op: 'unset', path: ['providers', 'ant-team-openai-responses'] },
2333
- { op: 'unset', path: ['providers', 'ant-team-openai'] },
2334
- { op: 'unset', path: ['providers', 'ant-team-anthropic'] },
2335
- ], undefined)
2371
+ let managedRoutes = []
2372
+ try {
2373
+ const parsed = JSON.parse(localStorage.getItem(PROVIDER_ROUTES_STORAGE_KEY) || '[]')
2374
+ if (Array.isArray(parsed)) managedRoutes = parsed.filter((route) => typeof route === 'string')
2375
+ } catch {}
2376
+ if (typeof remote?.settings?.describe === 'function') {
2377
+ const described = await remote.settings.describe()
2378
+ if (!described?.ok) throw new Error(described?.error?.message || '无法读取 DSH 中的 ANT 提供方列表。')
2379
+ const providers = described.value?.namespaces?.find((item) => item.ns === 'llm-pi-ai')?.value?.providers
2380
+ for (const [route, provider] of Object.entries(providers || {})) {
2381
+ if (route.startsWith('ant-team-') && provider?.apiKeyEnv === 'ANT_MODEL_API_KEY') managedRoutes.push(route)
2382
+ }
2383
+ }
2384
+ const routes = new Set([...managedRoutes, 'ant-team-openai-chat-completions', 'ant-team-openai-responses',
2385
+ 'ant-team-openai', 'ant-team-anthropic'])
2386
+ const settingsResult = await remote.settings.mutate('llm-pi-ai', [...routes].map((route) => ({
2387
+ op: 'unset', path: ['providers', route],
2388
+ })), undefined)
2336
2389
  if (!settingsResult.ok) throw new Error(settingsResult.error?.message || 'ANT 模型设置未能清除。')
2390
+ try { localStorage.removeItem(PROVIDER_ROUTES_STORAGE_KEY) } catch {}
2337
2391
  if (typeof remote?.credentials?.unset !== 'function') throw new Error('DSH 未提供凭证删除接口。')
2338
2392
  const credentialResult = await remote.credentials.unset('ANT_MODEL_API_KEY')
2339
2393
  if (!credentialResult.ok) throw new Error(credentialResult.error?.message || 'ANT 成员凭证未能清除。')
@@ -2500,7 +2554,7 @@ window.__ModuleLoader__.load({
2500
2554
  localStorage.setItem(SERVICE_URL_STORAGE_KEY, normalized)
2501
2555
  const response = await fetch('/api/ant-teams/connect', {
2502
2556
  method: 'POST',
2503
- headers: { 'Content-Type': 'application/json' },
2557
+ headers: { 'Content-Type': 'application/json', 'x-ant-model-provider-config': '1' },
2504
2558
  body: JSON.stringify({
2505
2559
  service_url: normalized,
2506
2560
  email: normalizedEmail,
@@ -2551,13 +2605,13 @@ window.__ModuleLoader__.load({
2551
2605
  const updateGuideUrl = pinnedServiceUrl
2552
2606
  ? new URL('/ant-update', pinnedServiceUrl).href : null
2553
2607
  const currentDshDefaultModel = modelForProviderSelection(dshDefaultSelection, authorizedModels)
2554
- const modelRows = authorizedModels.map((model) => h('div', { key: `${model.protocol}:${model.model_id}`, role: 'listitem',
2608
+ const modelRows = authorizedModels.map((model) => h('div', { key: `${model.provider_id || ''}:${model.protocol}:${model.model_id}`, role: 'listitem',
2555
2609
  style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(120px, auto) minmax(130px, auto)',
2556
2610
  gap: 12, alignItems: 'center', padding: '10px 2px', borderBottom: '1px solid var(--dsw-alias-border-l2)' } },
2557
- h('strong', { style: { minWidth: 0, overflowWrap: 'anywhere' } }, model.display_name || model.model_id),
2611
+ h('strong', { style: { minWidth: 0, overflowWrap: 'anywhere' } }, `${model.provider_name ? `${model.provider_name} · ` : ''}${model.display_name || model.model_id}`),
2558
2612
  h('span', { style: { display: 'inline-flex', justifySelf: 'start', padding: '3px 8px', borderRadius: 999,
2559
2613
  backgroundColor: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-secondary)', fontSize: 12 } },
2560
- model.protocol === 'anthropic' ? 'Anthropic Messages' : `OpenAI · ${(model.openai_api_mode || 'chat_completions') === 'responses' ? 'Responses' : 'Chat Completions'}`),
2614
+ model.protocol === 'anthropic' ? 'Anthropic Messages' : `OpenAI · ${model.openai_api_mode === 'responses' ? 'Responses' : 'Chat Completions'}`),
2561
2615
  h('span', { style: { ...mutedTextStyle, textAlign: 'right' } }, '已同步到 DSH')))
2562
2616
  const connectionPanel = h('section', { style: { ...settingsPanelStyle, display: activeTab === 'connection' ? 'grid' : 'none' } },
2563
2617
  h('div', null,
@@ -2634,7 +2688,7 @@ window.__ModuleLoader__.load({
2634
2688
  onChange: (event) => setDefaultModelDraft(event.target.value), style: inputStyle },
2635
2689
  h('option', { value: '' }, '选择获准模型'),
2636
2690
  ...authorizedModels.map((model) => h('option', { key: modelSelectionKey(model), value: modelSelectionKey(model) },
2637
- `${model.display_name || model.model_id} · ${model.protocol === 'anthropic' ? 'Anthropic' : (model.openai_api_mode || 'chat_completions') === 'responses' ? 'OpenAI Responses' : 'OpenAI Chat Completions'}`)))),
2691
+ `${model.provider_name ? `${model.provider_name} · ` : ''}${model.display_name || model.model_id} · ${model.protocol === 'anthropic' ? 'Anthropic' : model.openai_api_mode === 'responses' ? 'OpenAI Responses' : 'OpenAI Chat Completions'}`)))),
2638
2692
  h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' } },
2639
2693
  h('button', { type: 'button', disabled: defaultModelBusy || !defaultModelDraft,
2640
2694
  onClick: () => { const chosen = authorizedModels.find((model) => modelSelectionKey(model) === defaultModelDraft); void applyUserDefaultModel(chosen) },
package/conversation.js CHANGED
@@ -167,8 +167,14 @@ async function prompt(ctx, sessionId, content, signal) {
167
167
  }
168
168
 
169
169
  export function teamModelProvider(model) {
170
- if (model.protocol === 'anthropic') return 'ant-team-anthropic'
171
- return model.openai_api_mode === 'responses' ? 'ant-team-openai-responses' : 'ant-team-openai-chat-completions'
170
+ if (model.protocol === 'anthropic') {
171
+ return !model.provider_id || model.provider_id === 'anthropic-default'
172
+ ? 'ant-team-anthropic' : `ant-team-anthropic-${model.provider_id}`
173
+ }
174
+ if (!model.provider_id || model.provider_id === 'openai-default') {
175
+ return model.openai_api_mode === 'responses' ? 'ant-team-openai-responses' : 'ant-team-openai-chat-completions'
176
+ }
177
+ return `ant-team-openai-${model.provider_id}`
172
178
  }
173
179
 
174
180
  function dshDefaultSelection(ctx) {
@@ -182,8 +188,9 @@ export async function modelFor(ctx, memberJson, connection) {
182
188
  const selection = dshDefaultSelection(ctx)
183
189
  const localDefault = selection && models.find((item) => item.model_id === selection.model
184
190
  && teamModelProvider(item) === selection.provider)
185
- const serverDefault = catalog.default_model && models.find((item) => item.protocol === catalog.default_model.protocol
186
- && item.model_id === catalog.default_model.model_id)
191
+ const serverDefault = catalog.default_model && models.find((item) =>
192
+ (catalog.default_model.provider_id ? item.provider_id === catalog.default_model.provider_id : item.protocol === catalog.default_model.protocol)
193
+ && item.protocol === catalog.default_model.protocol && item.model_id === catalog.default_model.model_id)
187
194
  const model = localDefault || serverDefault || (!Object.hasOwn(catalog, 'default_model') ? models[0] : null)
188
195
  if (!model && models.length) throw new Error('当前没有可用的 ANT 默认模型;请在插件“团队模型”中选择本机默认,或联系管理员检查授权。')
189
196
  if (!model) throw new Error('当前没有可用的团队模型。')
package/index.js CHANGED
@@ -69,8 +69,10 @@ function modelServiceBinding(settings) {
69
69
  let url
70
70
  try { url = new URL(provider.baseURL) } catch { return { configured, serviceUrl: null } }
71
71
  const localHttp = url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)
72
- if ((url.protocol !== 'https:' && !localHttp) || url.username || url.password || url.search || url.hash
73
- || !['/model-gateway/openai/v1', '/model-gateway/anthropic'].includes(url.pathname)) {
72
+ const knownRoute = ['/model-gateway/openai/v1', '/model-gateway/anthropic'].includes(url.pathname)
73
+ || /^\/model-gateway\/openai\/providers\/[A-Za-z0-9-]{1,64}\/v1$/u.test(url.pathname)
74
+ || /^\/model-gateway\/anthropic\/providers\/[A-Za-z0-9-]{1,64}$/u.test(url.pathname)
75
+ if ((url.protocol !== 'https:' && !localHttp) || url.username || url.password || url.search || url.hash || !knownRoute) {
74
76
  return { configured, serviceUrl: null }
75
77
  }
76
78
  origins.add(url.origin)
@@ -178,7 +180,11 @@ async function fetchMemberModels(request, credentials, settings) {
178
180
  }
179
181
  if (!session.ok) return jsonResponse(502, { message: '无法激活 ANT 模型会话。' })
180
182
  const upstream = await fetch(new URL('/api/me/models', serviceUrl), {
181
- headers: { authorization: `Bearer ${token}`, accept: 'application/json' },
183
+ headers: {
184
+ authorization: `Bearer ${token}`,
185
+ accept: 'application/json',
186
+ ...(request.headers.get('x-ant-model-provider-config') === '1' ? { 'x-ant-model-provider-config': '1' } : {}),
187
+ },
182
188
  redirect: 'manual',
183
189
  signal: AbortSignal.timeout(10_000),
184
190
  })
@@ -865,6 +871,8 @@ async function fetchAuthRequest(request, endpoint, credentials, settings) {
865
871
  accept: 'application/json',
866
872
  'content-type': 'application/json',
867
873
  origin: serviceUrl,
874
+ ...(endpoint === 'dsh-connect' && request.headers.get('x-ant-model-provider-config') === '1'
875
+ ? { 'x-ant-model-provider-config': '1' } : {}),
868
876
  },
869
877
  body: JSON.stringify(payload),
870
878
  redirect: 'manual',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-wendao/ant",
3
- "version": "0.4.11",
3
+ "version": "0.4.12",
4
4
  "description": "ANT (AI-native Teams) connects a DeepSeek Harness Desktop profile to team identity and approved models.",
5
5
  "type": "module",
6
6
  "scripts": {