From 402ca4d7974c7917ca32f5a211fc6fa46aa607c4 Mon Sep 17 00:00:00 2001 From: EvolinkAI Date: Wed, 17 Jun 2026 12:37:14 +0800 Subject: [PATCH] feat: add evolink openai provider --- .env.example | 5 ++ README.md | 8 ++ docs/users/configuration/auth.md | 8 ++ docs/users/quickstart.md | 8 ++ .../core/openaiContentGenerator/index.test.ts | 44 +++++++++++ .../src/core/openaiContentGenerator/index.ts | 9 +++ .../openaiContentGenerator/provider/README.md | 5 ++ .../provider/evolink.test.ts | 77 +++++++++++++++++++ .../provider/evolink.ts | 18 +++++ .../openaiContentGenerator/provider/index.ts | 1 + 10 files changed, 183 insertions(+) create mode 100644 packages/core/src/core/openaiContentGenerator/index.test.ts create mode 100644 packages/core/src/core/openaiContentGenerator/provider/evolink.test.ts create mode 100644 packages/core/src/core/openaiContentGenerator/provider/evolink.ts diff --git a/.env.example b/.env.example index 45f135da9..6e854cbd0 100644 --- a/.env.example +++ b/.env.example @@ -31,6 +31,11 @@ # OPENAI_API_KEY=sk-... # OPENAI_BASE_URL=https://api.openai.com/v1 # OPENAI_MODEL=gpt-4o +# +# EvoLink OpenAI-compatible example: +# OPENAI_API_KEY=evl-... +# OPENAI_BASE_URL=https://direct.evolink.ai/v1 +# OPENAI_MODEL=gpt-5.2 # ----------------------------------------------------------------------------- diff --git a/README.md b/README.md index fb7905959..10f1e2308 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,14 @@ export OPENAI_BASE_URL="https://api.openai.com/v1" # optional export OPENAI_MODEL="gpt-4o" # optional, swap in GameCoder-27B when running it locally ``` +For EvoLink, use its OpenAI-compatible language endpoint: + +```bash +export OPENAI_API_KEY="your-evolink-api-key" +export OPENAI_BASE_URL="https://direct.evolink.ai/v1" +export OPENAI_MODEL="gpt-5.2" +``` + #### Asset / GDD provider keys (image, video, audio, reasoning) Beyond the main agent LLM, OpenGame's asset-generation tools talk to image, diff --git a/docs/users/configuration/auth.md b/docs/users/configuration/auth.md index 31f208c82..a74c26fa6 100644 --- a/docs/users/configuration/auth.md +++ b/docs/users/configuration/auth.md @@ -57,6 +57,14 @@ export OPENAI_BASE_URL="https://api.openai.com/v1" # optional export OPENAI_MODEL="gpt-4o" # optional ``` +EvoLink exposes an OpenAI-compatible language API as well: + +```bash +export OPENAI_API_KEY="your-evolink-api-key" +export OPENAI_BASE_URL="https://direct.evolink.ai/v1" +export OPENAI_MODEL="gpt-5.2" +``` + #### Persisting env vars with `.env` / `.qwen/.env` OpenGame will auto-load environment variables from the **first** `.env` file it finds (variables are **not merged** across multiple files). diff --git a/docs/users/quickstart.md b/docs/users/quickstart.md index 7d4d80e05..0b01c1a30 100644 --- a/docs/users/quickstart.md +++ b/docs/users/quickstart.md @@ -37,6 +37,14 @@ export OPENAI_BASE_URL="https://api.openai.com/v1" # optional export OPENAI_MODEL="gpt-4o" # optional; swap in GameCoder-27B when running locally ``` +For EvoLink, point the same OpenAI-compatible settings at EvoLink's direct language endpoint: + +```bash +export OPENAI_API_KEY="your-evolink-api-key" +export OPENAI_BASE_URL="https://direct.evolink.ai/v1" +export OPENAI_MODEL="gpt-5.2" +``` + You can also run `/auth` inside an interactive session to switch authentication methods. ## Step 3: Start your first session diff --git a/packages/core/src/core/openaiContentGenerator/index.test.ts b/packages/core/src/core/openaiContentGenerator/index.test.ts new file mode 100644 index 000000000..731fbd238 --- /dev/null +++ b/packages/core/src/core/openaiContentGenerator/index.test.ts @@ -0,0 +1,44 @@ +/** + * @license + * Copyright 2025 Qwen + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect, vi } from 'vitest'; +import type { Config } from '../../config/config.js'; +import type { ContentGeneratorConfig } from '../contentGenerator.js'; +import { determineProvider } from './index.js'; +import { + DefaultOpenAICompatibleProvider, + EvoLinkOpenAICompatibleProvider, +} from './provider/index.js'; + +describe('determineProvider', () => { + const cliConfig = { + getCliVersion: vi.fn().mockReturnValue('1.0.0'), + } as unknown as Config; + + it('returns the EvoLink provider for EvoLink direct API URLs', () => { + const config = { + apiKey: 'test-api-key', + baseUrl: 'https://direct.evolink.ai/v1', + model: 'gpt-5.2', + } as ContentGeneratorConfig; + + const provider = determineProvider(config, cliConfig); + + expect(provider).toBeInstanceOf(EvoLinkOpenAICompatibleProvider); + }); + + it('falls back to the default provider for generic OpenAI-compatible URLs', () => { + const config = { + apiKey: 'test-api-key', + baseUrl: 'https://api.example.com/v1', + model: 'gpt-4o', + } as ContentGeneratorConfig; + + const provider = determineProvider(config, cliConfig); + + expect(provider).toBeInstanceOf(DefaultOpenAICompatibleProvider); + }); +}); diff --git a/packages/core/src/core/openaiContentGenerator/index.ts b/packages/core/src/core/openaiContentGenerator/index.ts index fee32a049..5098b4a0f 100644 --- a/packages/core/src/core/openaiContentGenerator/index.ts +++ b/packages/core/src/core/openaiContentGenerator/index.ts @@ -13,6 +13,7 @@ import { OpenAIContentGenerator } from './openaiContentGenerator.js'; import { DashScopeOpenAICompatibleProvider, DeepSeekOpenAICompatibleProvider, + EvoLinkOpenAICompatibleProvider, ModelScopeOpenAICompatibleProvider, OpenRouterOpenAICompatibleProvider, type OpenAICompatibleProvider, @@ -26,6 +27,7 @@ export { type OpenAICompatibleProvider, DashScopeOpenAICompatibleProvider, DeepSeekOpenAICompatibleProvider, + EvoLinkOpenAICompatibleProvider, OpenRouterOpenAICompatibleProvider, } from './provider/index.js'; @@ -71,6 +73,13 @@ export function determineProvider( ); } + if (EvoLinkOpenAICompatibleProvider.isEvoLinkProvider(config)) { + return new EvoLinkOpenAICompatibleProvider( + contentGeneratorConfig, + cliConfig, + ); + } + // Check for OpenRouter provider if (OpenRouterOpenAICompatibleProvider.isOpenRouterProvider(config)) { return new OpenRouterOpenAICompatibleProvider( diff --git a/packages/core/src/core/openaiContentGenerator/provider/README.md b/packages/core/src/core/openaiContentGenerator/provider/README.md index 6133f0baa..90418a66a 100644 --- a/packages/core/src/core/openaiContentGenerator/provider/README.md +++ b/packages/core/src/core/openaiContentGenerator/provider/README.md @@ -8,6 +8,7 @@ This folder contains the different provider implementations for the Qwen Code re - `types.ts` - Type definitions and interfaces for providers - `default.ts` - Default provider for standard OpenAI-compatible APIs - `dashscope.ts` - DashScope (Qwen) specific provider implementation +- `evolink.ts` - EvoLink OpenAI-compatible provider implementation - `openrouter.ts` - OpenRouter specific provider implementation - `index.ts` - Main export file for all providers @@ -25,6 +26,10 @@ The `DashScopeOpenAICompatibleProvider` handles DashScope (Qwen) specific featur The `OpenRouterOpenAICompatibleProvider` handles OpenRouter specific headers and configurations. +### EvoLink Provider + +The `EvoLinkOpenAICompatibleProvider` handles EvoLink's OpenAI-compatible direct language API at `https://direct.evolink.ai/v1`. + ## Adding a New Provider To add a new provider: diff --git a/packages/core/src/core/openaiContentGenerator/provider/evolink.test.ts b/packages/core/src/core/openaiContentGenerator/provider/evolink.test.ts new file mode 100644 index 000000000..36d0d78f1 --- /dev/null +++ b/packages/core/src/core/openaiContentGenerator/provider/evolink.test.ts @@ -0,0 +1,77 @@ +/** + * @license + * Copyright 2025 Qwen + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { DefaultOpenAICompatibleProvider } from './default.js'; +import { EvoLinkOpenAICompatibleProvider } from './evolink.js'; +import type { Config } from '../../../config/config.js'; +import type { ContentGeneratorConfig } from '../../contentGenerator.js'; + +describe('EvoLinkOpenAICompatibleProvider', () => { + let provider: EvoLinkOpenAICompatibleProvider; + let mockContentGeneratorConfig: ContentGeneratorConfig; + let mockCliConfig: Config; + + beforeEach(() => { + mockContentGeneratorConfig = { + apiKey: 'test-api-key', + baseUrl: 'https://direct.evolink.ai/v1', + model: 'gpt-5.2', + } as ContentGeneratorConfig; + + mockCliConfig = { + getCliVersion: vi.fn().mockReturnValue('1.0.0'), + } as unknown as Config; + + provider = new EvoLinkOpenAICompatibleProvider( + mockContentGeneratorConfig, + mockCliConfig, + ); + }); + + describe('constructor', () => { + it('extends the default OpenAI-compatible provider', () => { + expect(provider).toBeInstanceOf(DefaultOpenAICompatibleProvider); + expect(provider).toBeInstanceOf(EvoLinkOpenAICompatibleProvider); + }); + }); + + describe('isEvoLinkProvider', () => { + it('returns true for EvoLink direct API URLs', () => { + const configs = [ + { baseUrl: 'https://direct.evolink.ai/v1' }, + { baseUrl: 'https://direct.evolink.ai' }, + { baseUrl: 'HTTPS://DIRECT.EVOLINK.AI/V1' }, + ]; + + configs.forEach((config) => { + expect( + EvoLinkOpenAICompatibleProvider.isEvoLinkProvider( + config as ContentGeneratorConfig, + ), + ).toBe(true); + }); + }); + + it('returns false for non-EvoLink URLs', () => { + const configs = [ + { baseUrl: 'https://api.openai.com/v1' }, + { baseUrl: 'https://openrouter.ai/api/v1' }, + { baseUrl: 'https://api.evolink.ai' }, + { baseUrl: '' }, + { baseUrl: undefined }, + ]; + + configs.forEach((config) => { + expect( + EvoLinkOpenAICompatibleProvider.isEvoLinkProvider( + config as ContentGeneratorConfig, + ), + ).toBe(false); + }); + }); + }); +}); diff --git a/packages/core/src/core/openaiContentGenerator/provider/evolink.ts b/packages/core/src/core/openaiContentGenerator/provider/evolink.ts new file mode 100644 index 000000000..b65171891 --- /dev/null +++ b/packages/core/src/core/openaiContentGenerator/provider/evolink.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2025 Qwen + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { ContentGeneratorConfig } from '../../contentGenerator.js'; +import { DefaultOpenAICompatibleProvider } from './default.js'; + +export class EvoLinkOpenAICompatibleProvider extends DefaultOpenAICompatibleProvider { + static isEvoLinkProvider( + contentGeneratorConfig: ContentGeneratorConfig, + ): boolean { + const baseUrl = contentGeneratorConfig.baseUrl ?? ''; + + return baseUrl.toLowerCase().includes('direct.evolink.ai'); + } +} diff --git a/packages/core/src/core/openaiContentGenerator/provider/index.ts b/packages/core/src/core/openaiContentGenerator/provider/index.ts index cb33834dd..278d671b7 100644 --- a/packages/core/src/core/openaiContentGenerator/provider/index.ts +++ b/packages/core/src/core/openaiContentGenerator/provider/index.ts @@ -1,6 +1,7 @@ export { ModelScopeOpenAICompatibleProvider } from './modelscope.js'; export { DashScopeOpenAICompatibleProvider } from './dashscope.js'; export { DeepSeekOpenAICompatibleProvider } from './deepseek.js'; +export { EvoLinkOpenAICompatibleProvider } from './evolink.js'; export { OpenRouterOpenAICompatibleProvider } from './openrouter.js'; export { DefaultOpenAICompatibleProvider } from './default.js'; export type {