diff --git a/packages/client/workbench/src/settings/providers/__tests__/add-flow.test.tsx b/packages/client/workbench/src/settings/providers/__tests__/add-flow.test.tsx index 8d0fef32..747400e8 100644 --- a/packages/client/workbench/src/settings/providers/__tests__/add-flow.test.tsx +++ b/packages/client/workbench/src/settings/providers/__tests__/add-flow.test.tsx @@ -164,4 +164,35 @@ describe('non-subscription account creation', () => { ), ); }); + + it.each([ + ['openai-chat', 'https://api.deepseek.com', 0], + ['openai-responses', 'https://api.deepseek.com', 1], + ['anthropic', 'https://api.deepseek.com/anthropic', 2], + ])('creates a DeepSeek account with its %s endpoint', async (protocol, baseUrl, variantIndex) => { + const onSubmit = vi.fn(); + render( + , + ); + + fireEvent.click(screen.getAllByRole('radio').at(variantIndex)!); + fireEvent.change(screen.getByPlaceholderText('sk-…'), { target: { value: 'sk-deepseek' } }); + fireEvent.click(screen.getByRole('button', { name: 'form.submit' })); + await waitFor(() => + expect(onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ + service: 'deepseek', + credential: { type: 'api-key', key: 'sk-deepseek' }, + endpoint: { baseUrl, protocol }, + }), + ), + ); + }); }); diff --git a/packages/client/workbench/src/settings/providers/__tests__/capability.test.ts b/packages/client/workbench/src/settings/providers/__tests__/capability.test.ts index e3a83195..b92857f3 100644 --- a/packages/client/workbench/src/settings/providers/__tests__/capability.test.ts +++ b/packages/client/workbench/src/settings/providers/__tests__/capability.test.ts @@ -59,6 +59,27 @@ describe('bindingAvailability', () => { }); }); + it('routes each DeepSeek protocol through the matching native agent', () => { + const chat = account({ + service: 'deepseek', + endpoint: { baseUrl: 'https://api.deepseek.com', protocol: 'openai-chat' }, + }); + const responses = account({ + service: 'deepseek', + endpoint: { baseUrl: 'https://api.deepseek.com', protocol: 'openai-responses' }, + }); + const anthropic = account({ + service: 'deepseek', + endpoint: { baseUrl: 'https://api.deepseek.com/anthropic', protocol: 'anthropic' }, + }); + expect(bindingAvailability(chat, 'claude-code')).toEqual({ tier: 'translate' }); + expect(bindingAvailability(chat, 'codex')).toEqual({ tier: 'native' }); + expect(bindingAvailability(chat, 'opencode')).toEqual({ tier: 'native' }); + expect(bindingAvailability(chat, 'pi')).toEqual({ tier: 'native' }); + expect(bindingAvailability(responses, 'codex')).toEqual({ tier: 'native' }); + expect(bindingAvailability(anthropic, 'claude-code')).toEqual({ tier: 'native' }); + }); + it('binds Grok Build to xAI catalog accounts', () => { expect(bindingAvailability(account({ service: 'xai' }), 'grok-build')).toEqual({ tier: 'native', @@ -131,7 +152,8 @@ describe('catalog helpers', () => { it('implies a protocol from the service when the endpoint is absent', () => { expect(accountProtocol(account({ service: 'anthropic-api' }))).toBe('anthropic'); expect(accountProtocol(account({ service: 'xai' }))).toBe('openai-chat'); - // Dual-protocol gateways imply nothing without an endpoint. + // Multi-protocol services imply nothing without an endpoint. + expect(accountProtocol(account({ service: 'deepseek' }))).toBeUndefined(); expect(accountProtocol(account({ service: 'openrouter' }))).toBeUndefined(); expect(accountProtocol(account({}))).toBeUndefined(); // An explicit endpoint always wins. @@ -144,4 +166,34 @@ describe('catalog helpers', () => { ), ).toBe('anthropic'); }); + + it('seeds all official DeepSeek protocol endpoints', () => { + const deepseek = serviceById('deepseek'); + expect(deepseek).toMatchObject({ + id: 'deepseek', + group: 'direct', + kind: 'endpoint', + }); + if (deepseek?.kind !== 'endpoint') throw new Error('deepseek descriptor missing'); + expect(deepseek.variants).toEqual([ + { + id: 'openai', + protocol: 'openai-chat', + baseUrl: 'https://api.deepseek.com', + credentialType: 'api-key', + }, + { + id: 'responses', + protocol: 'openai-responses', + baseUrl: 'https://api.deepseek.com', + credentialType: 'api-key', + }, + { + id: 'anthropic', + protocol: 'anthropic', + baseUrl: 'https://api.deepseek.com/anthropic', + credentialType: 'api-key', + }, + ]); + }); }); diff --git a/packages/client/workbench/src/settings/providers/capability.ts b/packages/client/workbench/src/settings/providers/capability.ts index 6e571c92..c7e2069c 100644 --- a/packages/client/workbench/src/settings/providers/capability.ts +++ b/packages/client/workbench/src/settings/providers/capability.ts @@ -7,7 +7,7 @@ import { accountProtocol } from './catalog'; * adapters, packages/host/engine `translator.ts`): * - claude-code speaks Anthropic natively (`ANTHROPIC_*` env); an `openai-chat` endpoint is * reachable through the local aigateway translator, which needs a base URL AND a key. - * - codex reaches any OpenAI-shaped endpoint via `OPENAI_BASE_URL` + `CODEX_API_KEY`. + * - codex reaches Responses and compatible Chat endpoints via `OPENAI_BASE_URL` + `CODEX_API_KEY`. * - opencode and pi: openai-chat is the verified path; an Anthropic endpoint plausibly works but * is unverified against the SDK — flip it here once verified live. * - grok-build only consumes an xAI key as `XAI_API_KEY`; it ignores custom endpoint base URLs. diff --git a/packages/client/workbench/src/settings/providers/catalog.ts b/packages/client/workbench/src/settings/providers/catalog.ts index 5fbfc98b..9ef1b46f 100644 --- a/packages/client/workbench/src/settings/providers/catalog.ts +++ b/packages/client/workbench/src/settings/providers/catalog.ts @@ -17,9 +17,9 @@ import type { /** Grouping in the add-account catalog grid. */ export type ServiceGroup = 'subscription' | 'direct' | 'gateway' | 'custom'; -/** One endpoint shape a service offers; dual-shape gateways expose one variant per protocol. */ +/** One endpoint shape a service offers; multi-protocol services expose one variant per protocol. */ export interface ServiceVariant { - id: 'openai' | 'anthropic' | 'default'; + id: 'openai' | 'responses' | 'anthropic' | 'default'; protocol: AccountProtocol; /** Endpoint URL, possibly templated with `{placeholder}` segments. */ baseUrl: string; @@ -75,6 +75,33 @@ export const SERVICE_CATALOG: ServiceDescriptor[] = [ ], secretPlaceholder: 'sk-…', }, + { + id: 'deepseek', + label: 'DeepSeek', + group: 'direct', + kind: 'endpoint', + variants: [ + { + id: 'openai', + protocol: 'openai-chat', + baseUrl: 'https://api.deepseek.com', + credentialType: 'api-key', + }, + { + id: 'responses', + protocol: 'openai-responses', + baseUrl: 'https://api.deepseek.com', + credentialType: 'api-key', + }, + { + id: 'anthropic', + protocol: 'anthropic', + baseUrl: 'https://api.deepseek.com/anthropic', + credentialType: 'api-key', + }, + ], + secretPlaceholder: 'sk-…', + }, { id: 'xai', label: 'xAI (Grok)', diff --git a/packages/presentation/i18n/src/locales/en.ts b/packages/presentation/i18n/src/locales/en.ts index 4f58424f..e4357f75 100644 --- a/packages/presentation/i18n/src/locales/en.ts +++ b/packages/presentation/i18n/src/locales/en.ts @@ -1059,6 +1059,7 @@ export const en = { 'chatgpt-sub': 'ChatGPT subscription', 'anthropic-api': 'Anthropic API', 'openai-api': 'OpenAI API', + deepseek: 'DeepSeek', xai: 'xAI (Grok)', openrouter: 'OpenRouter', 'vercel-gateway': 'Vercel AI Gateway', @@ -1070,6 +1071,7 @@ export const en = { 'chatgpt-sub': 'Codex login, Plus / Pro plans', 'anthropic-api': 'A key from console.anthropic.com', 'openai-api': 'A key from platform.openai.com', + deepseek: 'A DeepSeek API key, all three protocol shapes', xai: 'A key from console.x.ai, OpenAI-compatible', openrouter: 'Aggregation gateway, both protocol shapes', 'vercel-gateway': 'Server-side translation, both protocol shapes', diff --git a/packages/presentation/i18n/src/locales/zh-cn.ts b/packages/presentation/i18n/src/locales/zh-cn.ts index 59cf01fd..496abaf1 100644 --- a/packages/presentation/i18n/src/locales/zh-cn.ts +++ b/packages/presentation/i18n/src/locales/zh-cn.ts @@ -1033,6 +1033,7 @@ export const zhCN = { 'chatgpt-sub': 'ChatGPT 订阅', 'anthropic-api': 'Anthropic API', 'openai-api': 'OpenAI API', + deepseek: 'DeepSeek', xai: 'xAI(Grok)', openrouter: 'OpenRouter', 'vercel-gateway': 'Vercel AI Gateway', @@ -1044,6 +1045,7 @@ export const zhCN = { 'chatgpt-sub': 'Codex 登录,Plus / Pro 计划', 'anthropic-api': 'console.anthropic.com 的密钥', 'openai-api': 'platform.openai.com 的密钥', + deepseek: 'DeepSeek API 密钥,三种协议形态', xai: 'console.x.ai 的密钥,OpenAI 兼容', openrouter: '聚合网关,双协议形态', 'vercel-gateway': '服务端翻译,双协议形态',