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 461322c5..1de5d126 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 @@ -165,6 +165,40 @@ describe('non-subscription account creation', () => { ); }); + it('offers a dedicated StepFun API key entry', async () => { + const onPick = vi.fn(); + render(); + fireEvent.click(screen.getByText('serviceName.stepfun')); + expect(onPick).toHaveBeenCalledWith('stepfun'); + cleanup(); + + const onSubmit = vi.fn(); + render( + , + ); + + const secret = document.querySelector('input[type="password"]'); + if (!secret) throw new Error('credential input missing'); + fireEvent.change(secret, { target: { value: 'stepfun-test-key' } }); + fireEvent.click(screen.getByRole('button', { name: 'form.submit' })); + + await waitFor(() => + expect(onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ + service: 'stepfun', + credential: { type: 'api-key', key: 'stepfun-test-key' }, + }), + ), + ); + }); + it('asks nothing about protocol for a service serving several shapes', () => { render( { }); }); + it('routes a StepFun key over each supported native protocol', () => { + const stepfun = account({ service: 'stepfun' }); + expect(resolveBinding(stepfun, 'claude-code')).toEqual({ + tier: 'native', + protocol: 'anthropic', + baseUrl: 'https://api.stepfun.com', + }); + expect(resolveBinding(stepfun, 'codex')).toEqual({ + tier: 'native', + protocol: 'openai-responses', + baseUrl: 'https://api.stepfun.com/v1', + }); + expect(resolveBinding(stepfun, 'opencode')).toEqual({ + tier: 'native', + protocol: 'openai-chat', + baseUrl: 'https://api.stepfun.com/v1', + knownProvider: 'stepfun', + }); + expect(resolveBinding(stepfun, 'pi')).toEqual({ + tier: 'native', + protocol: 'anthropic', + baseUrl: 'https://api.stepfun.com', + }); + expect(resolveBinding(stepfun, 'grok-build')).toEqual({ + tier: 'unavailable', + reason: 'protocol-unsupported', + }); + }); + it('reaches codex on every service whose endpoint serves the Responses API', () => { // Verified against vendor docs 2026-08: xAI, OpenRouter and Vercel all serve POST /responses // at the base URL declared here, so codex must have a target on each. @@ -305,6 +334,10 @@ describe('catalog helpers', () => { url: 'https://api.deepseek.com/models', wire: 'openai', }); + expect(modelListSource('stepfun')).toEqual({ + url: 'https://api.stepfun.com/v1/models', + wire: 'openai', + }); expect(modelListSource('anthropic-api')?.wire).toBe('anthropic'); expect(modelListSource('linkcode-gateway')).toEqual({ url: 'https://gateway.linkcode.ai/v1/models', diff --git a/packages/foundation/providers/src/catalog.ts b/packages/foundation/providers/src/catalog.ts index 0dd7653d..c375ce20 100644 --- a/packages/foundation/providers/src/catalog.ts +++ b/packages/foundation/providers/src/catalog.ts @@ -133,6 +133,24 @@ export const SERVICE_CATALOG: ServiceDescriptor[] = [ models: { url: 'https://api.deepseek.com/models', wire: 'openai' }, secretPlaceholder: 'sk-…', }, + { + id: 'stepfun', + label: 'StepFun', + group: 'direct', + kind: 'endpoint', + credentialType: 'api-key', + variants: { + anthropic: { baseUrl: 'https://api.stepfun.com' }, + // Responses serves `step-3.7-flash` only (2026-08). + 'openai-responses': { baseUrl: 'https://api.stepfun.com/v1' }, + 'openai-chat': { + baseUrl: 'https://api.stepfun.com/v1', + knownProvider: { opencode: 'stepfun' }, + }, + }, + models: { url: 'https://api.stepfun.com/v1/models', wire: 'openai' }, + secretPlaceholder: 'sk-…', + }, { id: LINKCODE_GATEWAY_SERVICE_ID, label: 'LinkCode Gateway', diff --git a/packages/presentation/i18n/src/locales/en.ts b/packages/presentation/i18n/src/locales/en.ts index 56de73e2..6afb6446 100644 --- a/packages/presentation/i18n/src/locales/en.ts +++ b/packages/presentation/i18n/src/locales/en.ts @@ -1116,6 +1116,7 @@ export const en = { 'openai-api': 'OpenAI API', xai: 'xAI (Grok)', deepseek: 'DeepSeek', + stepfun: 'StepFun', openrouter: 'OpenRouter', 'vercel-gateway': 'Vercel AI Gateway', 'cloudflare-gateway': 'Cloudflare AI Gateway', @@ -1130,6 +1131,7 @@ export const en = { 'openai-api': 'A key from platform.openai.com', xai: 'A key from console.x.ai', deepseek: 'A key from platform.deepseek.com', + stepfun: 'A key from platform.stepfun.com', 'linkcode-gateway': 'Use LinkCode credits without bringing an API key', openrouter: 'Aggregation gateway', 'vercel-gateway': 'Server-side translating gateway', diff --git a/packages/presentation/i18n/src/locales/zh-cn.ts b/packages/presentation/i18n/src/locales/zh-cn.ts index ea900389..6dda1827 100644 --- a/packages/presentation/i18n/src/locales/zh-cn.ts +++ b/packages/presentation/i18n/src/locales/zh-cn.ts @@ -1089,6 +1089,7 @@ export const zhCN = { 'openai-api': 'OpenAI API', xai: 'xAI(Grok)', deepseek: 'DeepSeek', + stepfun: '阶跃星辰 StepFun', openrouter: 'OpenRouter', 'vercel-gateway': 'Vercel AI Gateway', 'cloudflare-gateway': 'Cloudflare AI Gateway', @@ -1103,6 +1104,7 @@ export const zhCN = { 'openai-api': 'platform.openai.com 的密钥', xai: 'console.x.ai 的密钥', deepseek: 'platform.deepseek.com 的密钥', + stepfun: 'platform.stepfun.com 的密钥', 'linkcode-gateway': '无需自带 API 密钥,直接使用 LinkCode 额度', openrouter: '聚合网关', 'vercel-gateway': '服务端翻译网关', diff --git a/packages/presentation/ui/src/shell/service-icon.tsx b/packages/presentation/ui/src/shell/service-icon.tsx index 6a72d3c5..5c011a89 100644 --- a/packages/presentation/ui/src/shell/service-icon.tsx +++ b/packages/presentation/ui/src/shell/service-icon.tsx @@ -6,6 +6,7 @@ import CloudflareColorGlyph from '~icons/lobe-icons/cloudflare-color'; import DeepSeekColorGlyph from '~icons/lobe-icons/deepseek-color'; import OpenAiGlyph from '~icons/lobe-icons/openai'; import OpenRouterGlyph from '~icons/lobe-icons/openrouter'; +import StepFunColorGlyph from '~icons/lobe-icons/stepfun-color'; import VercelGlyph from '~icons/lobe-icons/vercel'; import XaiGlyph from '~icons/lobe-icons/xai'; import { cn } from '../lib/cn'; @@ -32,6 +33,7 @@ const SERVICE_GLYPHS: Record = { 'openai-api': OpenAiGlyph, xai: XaiGlyph, deepseek: DeepSeekColorGlyph, + stepfun: StepFunColorGlyph, openrouter: OpenRouterGlyph, 'vercel-gateway': VercelGlyph, 'cloudflare-gateway': CloudflareColorGlyph,