From 61267cda80ee6e2a8f8003a1302e66083e15059e Mon Sep 17 00:00:00 2001 From: KaiShaoCheng Date: Sun, 14 Jun 2026 15:24:27 +0800 Subject: [PATCH 1/3] feat(provider): add AGIone provider preset --- electron/shared/providers/registry.ts | 22 +++++++++++++++ electron/shared/providers/types.ts | 2 ++ shared/host-api/contract.ts | 1 + src/lib/providers.ts | 3 +++ tests/unit/providers.test.ts | 39 +++++++++++++++++++++++++-- 5 files changed, 65 insertions(+), 2 deletions(-) diff --git a/electron/shared/providers/registry.ts b/electron/shared/providers/registry.ts index eabf3ed0c..108fec275 100644 --- a/electron/shared/providers/registry.ts +++ b/electron/shared/providers/registry.ts @@ -86,6 +86,28 @@ export const PROVIDER_DEFINITIONS: ProviderDefinition[] = [ }, }, }, + { + id: 'agione', + name: 'AGIone', + icon: '🧩', + placeholder: 'ak-...', + model: 'Multi-Model', + requiresApiKey: true, + defaultBaseUrl: 'https://agione.pro/hyperone/xapi/api/v1', + showModelId: true, + modelIdPlaceholder: 'qwen/qwen3.5-plus/cec84', + defaultModelId: 'qwen/qwen3.5-plus/cec84', + category: 'compatible', + envVar: 'AGIONE_API_KEY', + supportedAuthModes: ['api_key'], + defaultAuthMode: 'api_key', + supportsMultipleAccounts: true, + providerConfig: { + baseUrl: 'https://agione.pro/hyperone/xapi/api/v1', + api: 'openai-completions', + apiKeyEnv: 'AGIONE_API_KEY', + }, + }, { id: 'ark', name: 'ByteDance Ark', diff --git a/electron/shared/providers/types.ts b/electron/shared/providers/types.ts index 08beb43ce..aff16d0ea 100644 --- a/electron/shared/providers/types.ts +++ b/electron/shared/providers/types.ts @@ -3,6 +3,7 @@ export const PROVIDER_TYPES = [ 'openai', 'google', 'openrouter', + 'agione', 'ark', 'moonshot', 'moonshot-global', @@ -20,6 +21,7 @@ export const BUILTIN_PROVIDER_TYPES = [ 'openai', 'google', 'openrouter', + 'agione', 'ark', 'moonshot', 'moonshot-global', diff --git a/shared/host-api/contract.ts b/shared/host-api/contract.ts index d24012194..4dcbd90bb 100644 --- a/shared/host-api/contract.ts +++ b/shared/host-api/contract.ts @@ -251,6 +251,7 @@ export type ProviderType = | 'openai' | 'google' | 'openrouter' + | 'agione' | 'ark' | 'moonshot' | 'moonshot-global' diff --git a/src/lib/providers.ts b/src/lib/providers.ts index d692fef5e..7039d1b8d 100644 --- a/src/lib/providers.ts +++ b/src/lib/providers.ts @@ -11,6 +11,7 @@ export const PROVIDER_TYPES = [ 'openai', 'google', 'openrouter', + 'agione', 'ark', 'moonshot', 'moonshot-global', @@ -40,6 +41,7 @@ export const BUILTIN_PROVIDER_TYPES = [ 'openai', 'google', 'openrouter', + 'agione', 'ark', 'moonshot', 'moonshot-global', @@ -186,6 +188,7 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ apiKeyUrl: 'https://aistudio.google.com/app/apikey', }, { id: 'openrouter', name: 'OpenRouter', icon: '🌐', placeholder: 'sk-or-v1-...', model: 'Multi-Model', requiresApiKey: true, showModelId: true, modelIdPlaceholder: 'openai/gpt-5.5', defaultModelId: 'openai/gpt-5.5', docsUrl: 'https://openrouter.ai/models' }, + { id: 'agione', name: 'AGIone', icon: '🧩', placeholder: 'ak-...', model: 'Multi-Model', requiresApiKey: true, defaultBaseUrl: 'https://agione.pro/hyperone/xapi/api/v1', showModelId: true, modelIdPlaceholder: 'qwen/qwen3.5-plus/cec84', defaultModelId: 'qwen/qwen3.5-plus/cec84', apiKeyUrl: 'https://agione.pro', docsUrl: 'https://agione.pro' }, { id: 'minimax-portal-cn', name: 'MiniMax (CN)', icon: '☁️', placeholder: 'sk-...', model: 'MiniMax', requiresApiKey: false, isOAuth: true, supportsApiKey: true, defaultModelId: 'MiniMax-M3', showModelId: true, modelIdPlaceholder: 'MiniMax-M3', apiKeyUrl: 'https://platform.minimaxi.com/' }, { id: 'moonshot', name: 'Moonshot (CN)', icon: '🌙', placeholder: 'sk-...', model: 'Kimi', requiresApiKey: true, defaultBaseUrl: 'https://api.moonshot.cn/v1', showModelId: true, defaultModelId: 'kimi-k2.6', modelIdPlaceholder: 'kimi-k2.6', docsUrl: 'https://platform.moonshot.cn/' }, { id: 'moonshot-global', name: 'Moonshot (Global)', icon: '🌙', placeholder: 'sk-...', model: 'Kimi', requiresApiKey: true, defaultBaseUrl: 'https://api.moonshot.ai/v1', showModelId: true, defaultModelId: 'kimi-k2.6', modelIdPlaceholder: 'kimi-k2.6', docsUrl: 'https://platform.moonshot.ai/' }, diff --git a/tests/unit/providers.test.ts b/tests/unit/providers.test.ts index 38f2e5682..e26aa6583 100644 --- a/tests/unit/providers.test.ts +++ b/tests/unit/providers.test.ts @@ -46,6 +46,30 @@ describe('provider metadata', () => { }); }); + it('includes AGIone as an OpenAI-compatible provider', () => { + expect(PROVIDER_TYPES).toContain('agione'); + expect(BUILTIN_PROVIDER_TYPES).toContain('agione'); + expect(getProviderEnvVar('agione')).toBe('AGIONE_API_KEY'); + expect(getProviderConfig('agione')).toEqual({ + baseUrl: 'https://agione.pro/hyperone/xapi/api/v1', + api: 'openai-completions', + apiKeyEnv: 'AGIONE_API_KEY', + }); + + expect(PROVIDER_TYPE_INFO).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: 'agione', + name: 'AGIone', + defaultBaseUrl: 'https://agione.pro/hyperone/xapi/api/v1', + showModelId: true, + defaultModelId: 'qwen/qwen3.5-plus/cec84', + docsUrl: 'https://agione.pro', + }), + ]) + ); + }); + it('uses a single canonical env key for moonshot provider', () => { expect(getProviderEnvVar('moonshot')).toBe('MOONSHOT_API_KEY'); expect(getProviderEnvVars('moonshot')).toEqual(['MOONSHOT_API_KEY']); @@ -59,7 +83,7 @@ describe('provider metadata', () => { it('keeps builtin provider sources in sync', () => { expect(BUILTIN_PROVIDER_TYPES).toEqual( - expect.arrayContaining(['anthropic', 'openai', 'google', 'openrouter', 'ark', 'moonshot', 'siliconflow', 'minimax-portal', 'minimax-portal-cn', 'modelstudio', 'ollama']) + expect.arrayContaining(['anthropic', 'openai', 'google', 'openrouter', 'agione', 'ark', 'moonshot', 'siliconflow', 'minimax-portal', 'minimax-portal-cn', 'modelstudio', 'ollama']) ); }); @@ -80,6 +104,7 @@ describe('provider metadata', () => { it('exposes provider documentation links', () => { const anthropic = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'anthropic'); const openrouter = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'openrouter'); + const agione = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'agione'); const moonshot = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'moonshot'); const siliconflow = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'siliconflow'); const ark = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'ark'); @@ -90,6 +115,7 @@ describe('provider metadata', () => { }); expect(getProviderDocsUrl(anthropic, 'en')).toBe('https://platform.claude.com/docs/en/api/overview'); expect(getProviderDocsUrl(openrouter, 'en')).toBe('https://openrouter.ai/models'); + expect(getProviderDocsUrl(agione, 'en')).toBe('https://agione.pro'); expect(getProviderDocsUrl(moonshot, 'en')).toBe('https://platform.moonshot.cn/'); expect(getProviderDocsUrl(siliconflow, 'en')).toBe('https://docs.siliconflow.cn/cn/userguide/introduction'); expect(getProviderDocsUrl(ark, 'en')).toBe('https://www.volcengine.com/'); @@ -104,6 +130,7 @@ describe('provider metadata', () => { it('exposes editable model id with default for built-in providers, mirroring OpenRouter', () => { const anthropic = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'anthropic'); const openrouter = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'openrouter'); + const agione = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'agione'); const siliconflow = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'siliconflow'); const deepseek = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'deepseek'); const moonshot = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'moonshot'); @@ -118,6 +145,11 @@ describe('provider metadata', () => { showModelId: true, defaultModelId: 'openai/gpt-5.5', }); + expect(agione).toMatchObject({ + showModelId: true, + defaultModelId: 'qwen/qwen3.5-plus/cec84', + modelIdPlaceholder: 'qwen/qwen3.5-plus/cec84', + }); expect(siliconflow).toMatchObject({ showModelId: true, defaultModelId: 'deepseek-ai/DeepSeek-V3', @@ -137,7 +169,7 @@ describe('provider metadata', () => { modelIdPlaceholder: 'kimi-k2.6', }); - for (const provider of [anthropic, openrouter, siliconflow, deepseek, moonshot, moonshotGlobal]) { + for (const provider of [anthropic, openrouter, agione, siliconflow, deepseek, moonshot, moonshotGlobal]) { expect(provider?.showModelIdInDevModeOnly).toBeUndefined(); expect(shouldShowProviderModelId(provider, false)).toBe(true); expect(shouldShowProviderModelId(provider, true)).toBe(true); @@ -188,16 +220,19 @@ describe('provider metadata', () => { it('saves user-entered or default model overrides for built-in providers without dev mode', () => { const openrouter = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'openrouter'); + const agione = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'agione'); const siliconflow = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'siliconflow'); const anthropic = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'anthropic'); const ark = PROVIDER_TYPE_INFO.find((provider) => provider.id === 'ark'); expect(resolveProviderModelForSave(openrouter, 'openai/gpt-5', false)).toBe('openai/gpt-5'); + expect(resolveProviderModelForSave(agione, 'qwen/qwen3.5-plus/cec84', false)).toBe('qwen/qwen3.5-plus/cec84'); expect(resolveProviderModelForSave(siliconflow, 'Qwen/Qwen3-Coder-480B-A35B-Instruct', false)) .toBe('Qwen/Qwen3-Coder-480B-A35B-Instruct'); expect(resolveProviderModelForSave(anthropic, 'claude-sonnet-4-5', false)).toBe('claude-sonnet-4-5'); expect(resolveProviderModelForSave(openrouter, ' ', false)).toBe('openai/gpt-5.5'); + expect(resolveProviderModelForSave(agione, ' ', false)).toBe('qwen/qwen3.5-plus/cec84'); expect(resolveProviderModelForSave(siliconflow, ' ', false)).toBe('deepseek-ai/DeepSeek-V3'); expect(resolveProviderModelForSave(anthropic, ' ', false)).toBe('claude-opus-4-6'); expect(resolveProviderModelForSave(ark, ' ep-custom-model ', false)).toBe('ep-custom-model'); From 43bc0ae51544be12b0dcf6cfb18b4ad48d18e6e4 Mon Sep 17 00:00:00 2001 From: KaiShaoCheng Date: Sun, 14 Jun 2026 20:57:45 +0800 Subject: [PATCH 2/3] test(e2e): cover AGIone provider add flow --- tests/e2e/provider-lifecycle.spec.ts | 87 ++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/tests/e2e/provider-lifecycle.spec.ts b/tests/e2e/provider-lifecycle.spec.ts index 7fb4274d0..04569248d 100644 --- a/tests/e2e/provider-lifecycle.spec.ts +++ b/tests/e2e/provider-lifecycle.spec.ts @@ -127,6 +127,93 @@ test.describe('ClawX provider lifecycle', () => { await expect(page.getByTestId('add-provider-api-key-input')).toHaveCount(0); }); + test('adds AGIone from the add-provider dialog with its default model preset', async ({ electronApp, page }) => { + await completeSetup(page); + + await electronApp.evaluate(async ({ app: _app }) => { + const { ipcMain } = process.mainModule!.require('electron') as typeof import('electron'); + + let accounts: Array> = []; + let keyInfo: Array<{ accountId: string; hasKey: boolean; keyMasked: string | null }> = []; + let defaultAccountId: string | null = null; + const originalHostInvoke = (ipcMain as unknown as { + _invokeHandlers?: Map Promise>; + })._invokeHandlers?.get('host:invoke'); + + const respond = (id: unknown, data: unknown) => ({ + id: typeof id === 'string' ? id : undefined, + ok: true, + data, + }); + + ipcMain.removeHandler('host:invoke'); + ipcMain.handle('host:invoke', async (event: unknown, request: { + id?: string; + module?: string; + action?: string; + payload?: Record; + }) => { + if (request?.module !== 'providers') { + return originalHostInvoke?.(event, request) ?? respond(request?.id, undefined); + } + + const body = request.payload ?? {}; + if (request.action === 'accounts') return respond(request.id, accounts); + if (request.action === 'accountKeyInfo') return respond(request.id, keyInfo); + if (request.action === 'vendors') return respond(request.id, []); + if (request.action === 'getDefaultAccount') return respond(request.id, { accountId: defaultAccountId }); + if (request.action === 'list') return respond(request.id, []); + + if (request.action === 'validateKey') { + expect(body.providerId).toBe('agione'); + expect(body.apiKey).toBe('ak-agione-test'); + expect(body.options).toEqual({ + baseUrl: 'https://agione.pro/hyperone/xapi/api/v1', + }); + return respond(request.id, { valid: true }); + } + + if (request.action === 'createAccount') { + const account = body.account as Record; + expect(account.vendorId).toBe('agione'); + expect(account.label).toBe('AGIone'); + expect(account.baseUrl).toBe('https://agione.pro/hyperone/xapi/api/v1'); + expect(account.model).toBe('qwen/qwen3.5-plus/cec84'); + accounts = [account]; + keyInfo = [{ + accountId: String(account.id), + hasKey: Boolean(body.apiKey), + keyMasked: body.apiKey ? 'ak-***' : null, + }]; + return respond(request.id, { success: true, account }); + } + + if (request.action === 'setDefaultAccount') { + defaultAccountId = typeof body.accountId === 'string' ? body.accountId : null; + return respond(request.id, { success: true }); + } + + return respond(request.id, {}); + }); + }); + + await page.getByTestId('sidebar-nav-models').click(); + await expect(page.getByTestId('providers-settings')).toBeVisible(); + + await page.getByTestId('providers-add-button').click(); + await expect(page.getByTestId('add-provider-dialog')).toBeVisible(); + + await page.getByTestId('add-provider-type-agione').click(); + await expect(page.getByTestId('add-provider-name-input')).toHaveValue('AGIone'); + await expect(page.getByTestId('add-provider-model-id-input')).toHaveValue('qwen/qwen3.5-plus/cec84'); + + await page.getByTestId('add-provider-api-key-input').fill(' ak-agione-test\n'); + await page.getByTestId('add-provider-submit-button').click(); + + await expect(page.getByTestId('provider-card-agione')).toContainText('AGIone'); + await expect(page.getByTestId('provider-card-agione')).toContainText('qwen/qwen3.5-plus/cec84'); + }); + test('trims whitespace before validating and saving a custom provider key', async ({ electronApp, page }) => { await completeSetup(page); From 9714cbdeb83eea522f230c322b8f141dd1902b2f Mon Sep 17 00:00:00 2001 From: KaiShaoCheng Date: Tue, 16 Jun 2026 10:12:02 +0800 Subject: [PATCH 3/3] fix: address AGIone provider review feedback --- electron/shared/providers/registry.ts | 2 +- tests/e2e/provider-lifecycle.spec.ts | 74 +++++++++++++++++++++++---- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/electron/shared/providers/registry.ts b/electron/shared/providers/registry.ts index 108fec275..86b15499b 100644 --- a/electron/shared/providers/registry.ts +++ b/electron/shared/providers/registry.ts @@ -101,7 +101,7 @@ export const PROVIDER_DEFINITIONS: ProviderDefinition[] = [ envVar: 'AGIONE_API_KEY', supportedAuthModes: ['api_key'], defaultAuthMode: 'api_key', - supportsMultipleAccounts: true, + supportsMultipleAccounts: false, providerConfig: { baseUrl: 'https://agione.pro/hyperone/xapi/api/v1', api: 'openai-completions', diff --git a/tests/e2e/provider-lifecycle.spec.ts b/tests/e2e/provider-lifecycle.spec.ts index 04569248d..eeae50708 100644 --- a/tests/e2e/provider-lifecycle.spec.ts +++ b/tests/e2e/provider-lifecycle.spec.ts @@ -133,9 +133,28 @@ test.describe('ClawX provider lifecycle', () => { await electronApp.evaluate(async ({ app: _app }) => { const { ipcMain } = process.mainModule!.require('electron') as typeof import('electron'); + type AgioneProviderE2EState = { + validateRequests: Array<{ + providerId: unknown; + apiKey: unknown; + options: unknown; + }>; + createRequests: Array<{ + account: Record; + apiKey: unknown; + }>; + defaultAccountId: string | null; + }; + + const state: AgioneProviderE2EState = { + validateRequests: [], + createRequests: [], + defaultAccountId: null, + }; + (globalThis as unknown as { __agioneProviderE2E?: AgioneProviderE2EState }).__agioneProviderE2E = state; + let accounts: Array> = []; let keyInfo: Array<{ accountId: string; hasKey: boolean; keyMasked: string | null }> = []; - let defaultAccountId: string | null = null; const originalHostInvoke = (ipcMain as unknown as { _invokeHandlers?: Map Promise>; })._invokeHandlers?.get('host:invoke'); @@ -161,24 +180,21 @@ test.describe('ClawX provider lifecycle', () => { if (request.action === 'accounts') return respond(request.id, accounts); if (request.action === 'accountKeyInfo') return respond(request.id, keyInfo); if (request.action === 'vendors') return respond(request.id, []); - if (request.action === 'getDefaultAccount') return respond(request.id, { accountId: defaultAccountId }); + if (request.action === 'getDefaultAccount') return respond(request.id, { accountId: state.defaultAccountId }); if (request.action === 'list') return respond(request.id, []); if (request.action === 'validateKey') { - expect(body.providerId).toBe('agione'); - expect(body.apiKey).toBe('ak-agione-test'); - expect(body.options).toEqual({ - baseUrl: 'https://agione.pro/hyperone/xapi/api/v1', + state.validateRequests.push({ + providerId: body.providerId, + apiKey: body.apiKey, + options: body.options, }); return respond(request.id, { valid: true }); } if (request.action === 'createAccount') { const account = body.account as Record; - expect(account.vendorId).toBe('agione'); - expect(account.label).toBe('AGIone'); - expect(account.baseUrl).toBe('https://agione.pro/hyperone/xapi/api/v1'); - expect(account.model).toBe('qwen/qwen3.5-plus/cec84'); + state.createRequests.push({ account, apiKey: body.apiKey }); accounts = [account]; keyInfo = [{ accountId: String(account.id), @@ -189,7 +205,7 @@ test.describe('ClawX provider lifecycle', () => { } if (request.action === 'setDefaultAccount') { - defaultAccountId = typeof body.accountId === 'string' ? body.accountId : null; + state.defaultAccountId = typeof body.accountId === 'string' ? body.accountId : null; return respond(request.id, { success: true }); } @@ -212,6 +228,42 @@ test.describe('ClawX provider lifecycle', () => { await expect(page.getByTestId('provider-card-agione')).toContainText('AGIone'); await expect(page.getByTestId('provider-card-agione')).toContainText('qwen/qwen3.5-plus/cec84'); + + const observed = await electronApp.evaluate(() => ( + globalThis as unknown as { + __agioneProviderE2E?: { + validateRequests: Array>; + createRequests: Array<{ + account: Record; + apiKey: unknown; + }>; + defaultAccountId: string | null; + }; + } + ).__agioneProviderE2E); + + expect(observed?.validateRequests).toEqual([ + { + providerId: 'agione', + apiKey: 'ak-agione-test', + options: { + baseUrl: 'https://agione.pro/hyperone/xapi/api/v1', + }, + }, + ]); + expect(observed?.createRequests).toEqual([ + { + account: expect.objectContaining({ + id: 'agione', + vendorId: 'agione', + label: 'AGIone', + baseUrl: 'https://agione.pro/hyperone/xapi/api/v1', + model: 'qwen/qwen3.5-plus/cec84', + }), + apiKey: 'ak-agione-test', + }, + ]); + expect(observed?.defaultAccountId).toBe('agione'); }); test('trims whitespace before validating and saving a custom provider key', async ({ electronApp, page }) => {