diff --git a/README.jp.md b/README.jp.md index 6e436231f..5ce06caec 100644 --- a/README.jp.md +++ b/README.jp.md @@ -478,6 +478,10 @@ DeepChatはメッセージアプリからリモート操作できるため、デ Obscurify.ai Icon
Obscurify.ai + + Wally Icon
+ Wally + diff --git a/README.md b/README.md index 37d7c51f3..ae7ff4d41 100644 --- a/README.md +++ b/README.md @@ -481,6 +481,10 @@ Common commands include `/start`, `/help`, `/pair`, `/new`, `/sessions`, `/use`, Obscurify.ai Icon
Obscurify.ai + + Wally Icon
+ Wally + diff --git a/README.zh.md b/README.zh.md index 382995679..03a1a46bc 100644 --- a/README.zh.md +++ b/README.zh.md @@ -475,6 +475,10 @@ DeepChat 可以通过聊天软件远程控制,让你离开桌面后也能继 Obscurify.ai Icon
Obscurify.ai + + Wally Icon
+ Wally + diff --git a/src/main/provider/defaults.ts b/src/main/provider/defaults.ts index 0b25da295..e6d10c61e 100644 --- a/src/main/provider/defaults.ts +++ b/src/main/provider/defaults.ts @@ -706,6 +706,20 @@ const PROVIDER_DEFAULTS: LLM_PROVIDER_BASE[] = [ models: 'https://cloudgpu.app/api?utm_source=deepchat' } }, + { + id: 'wally', + name: 'Wally', + apiType: 'openai-completions', + apiKey: '', + baseUrl: 'https://inference.runanywhere.ai/v1', + enable: false, + websites: { + official: 'https://www.runwally.com/', + apiKey: 'https://console.runanywhere.ai/', + docs: 'https://docs.runanywhere.ai/wally-cloud/api', + models: 'https://www.runwally.com/pricing' + } + }, { id: 'cheapaimodel', name: 'cheapaimodel', diff --git a/src/main/provider/providerRegistry.ts b/src/main/provider/providerRegistry.ts index e3bba4837..66ba61bd4 100644 --- a/src/main/provider/providerRegistry.ts +++ b/src/main/provider/providerRegistry.ts @@ -665,6 +665,19 @@ const PROVIDER_ID_REGISTRY = new Map([ checkMaxTokens: 16 }) ], + [ + 'wally', + createDefinition({ + ...OPENAI_BASE, + checkStrategy: 'generate-text', + credentialStrategy: 'api-key', + embeddingStrategy: 'none', + checkModelId: 'deepseek-v4.1-flash', + checkPrompt: 'Hello', + checkTemperature: 0.2, + checkMaxTokens: 16 + }) + ], [ 'obscurify', createDefinition({ diff --git a/src/renderer/src/assets/llm-icons/wally-color.svg b/src/renderer/src/assets/llm-icons/wally-color.svg new file mode 100644 index 000000000..6e78052a1 --- /dev/null +++ b/src/renderer/src/assets/llm-icons/wally-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/renderer/src/components/icons/modelIconRegistry.ts b/src/renderer/src/components/icons/modelIconRegistry.ts index bbcfc5e9f..f6da27172 100644 --- a/src/renderer/src/components/icons/modelIconRegistry.ts +++ b/src/renderer/src/components/icons/modelIconRegistry.ts @@ -56,6 +56,7 @@ import opperColorIcon from '@/assets/llm-icons/opper.svg?url' import aistupidlevelIcon from '@/assets/llm-icons/aistupidlevel.svg?url' import runcrateIcon from '@/assets/llm-icons/runcrate.svg?url' import cloudgpuIcon from '@/assets/llm-icons/cloudgpu.svg?url' +import wallyColorIcon from '@/assets/llm-icons/wally-color.svg?url' import cheapaimodelIcon from '@/assets/llm-icons/cheapaimodel.svg?url' import obscurifyIcon from '@/assets/llm-icons/obscurify.svg?url' import demonrouteColorIcon from '@/assets/llm-icons/demonroute.svg?url' @@ -210,6 +211,7 @@ export const modelIcons = { aistupidlevel: aistupidlevelIcon, runcrate: runcrateIcon, cloudgpu: cloudgpuIcon, + wally: wallyColorIcon, cheapaimodel: cheapaimodelIcon, obscurify: obscurifyIcon, demonroute: demonrouteColorIcon, diff --git a/test/main/provider/basicApiKeyProviders.test.ts b/test/main/provider/basicApiKeyProviders.test.ts index 6cbabc8c6..f612baee6 100644 --- a/test/main/provider/basicApiKeyProviders.test.ts +++ b/test/main/provider/basicApiKeyProviders.test.ts @@ -1813,6 +1813,40 @@ describe('basic API-key provider registrations', () => { ) }) + it('checks Wally with a chat model instead of its public model list', async () => { + const defaults = DEFAULT_PROVIDERS.find((provider) => provider.id === 'wally')! + expect(defaults).toBeDefined() + const config = { ...defaults, apiKey: 'test-key' } + // Wally's /v1/models answers without a key, so listing models cannot validate one. + expect(resolveAiSdkProviderDefinition(config)).toMatchObject({ + runtimeKind: 'openai-compatible', + modelSource: 'openai', + checkStrategy: 'generate-text', + credentialStrategy: 'api-key', + embeddingStrategy: 'none', + checkModelId: 'deepseek-v4.1-flash' + }) + const provider = new AiSdkProvider(config, createProviderSettings()) + ;(provider as any).isInitialized = true + + await expect(provider.check()).resolves.toEqual({ isOk: true, errorMsg: null }) + expect(mockRunAiSdkGenerateText).toHaveBeenCalledWith( + expect.objectContaining({ + providerKind: 'openai-compatible', + provider: expect.objectContaining({ + id: 'wally', + apiKey: 'test-key', + baseUrl: 'https://inference.runanywhere.ai/v1' + }) + }), + [{ role: 'user', content: 'Hello' }], + 'deepseek-v4.1-flash', + expect.any(Object), + 0.2, + 16 + ) + }) + it('lists every CloudGPU catalog id except non-chat models', async () => { const defaults = DEFAULT_PROVIDERS.find(({ id }) => id === 'cloudgpu')! const fetchMock = vi.fn().mockResolvedValue( diff --git a/test/main/provider/defaultProviders.test.ts b/test/main/provider/defaultProviders.test.ts index 7fd2e1256..7afce6024 100644 --- a/test/main/provider/defaultProviders.test.ts +++ b/test/main/provider/defaultProviders.test.ts @@ -170,6 +170,25 @@ describe('DEFAULT_PROVIDERS', () => { ) }) + it('includes Wally as a disabled built-in OpenAI-compatible provider', () => { + expect(DEFAULT_PROVIDERS).toContainEqual( + expect.objectContaining({ + id: 'wally', + name: 'Wally', + apiType: 'openai-completions', + baseUrl: 'https://inference.runanywhere.ai/v1', + enable: false, + websites: expect.objectContaining({ + official: 'https://www.runwally.com/', + apiKey: 'https://console.runanywhere.ai/', + docs: 'https://docs.runanywhere.ai/wally-cloud/api', + models: 'https://www.runwally.com/pricing', + defaultBaseUrl: 'https://inference.runanywhere.ai/v1' + }) + }) + ) + }) + it('includes Obscurify.ai as a disabled built-in OpenAI-compatible provider', () => { expect(DEFAULT_PROVIDERS).toContainEqual( expect.objectContaining({