Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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


# -----------------------------------------------------------------------------
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions docs/users/configuration/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
8 changes: 8 additions & 0 deletions docs/users/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 44 additions & 0 deletions packages/core/src/core/openaiContentGenerator/index.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
9 changes: 9 additions & 0 deletions packages/core/src/core/openaiContentGenerator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { OpenAIContentGenerator } from './openaiContentGenerator.js';
import {
DashScopeOpenAICompatibleProvider,
DeepSeekOpenAICompatibleProvider,
EvoLinkOpenAICompatibleProvider,
ModelScopeOpenAICompatibleProvider,
OpenRouterOpenAICompatibleProvider,
type OpenAICompatibleProvider,
Expand All @@ -26,6 +27,7 @@ export {
type OpenAICompatibleProvider,
DashScopeOpenAICompatibleProvider,
DeepSeekOpenAICompatibleProvider,
EvoLinkOpenAICompatibleProvider,
OpenRouterOpenAICompatibleProvider,
} from './provider/index.js';

Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
});
18 changes: 18 additions & 0 deletions packages/core/src/core/openaiContentGenerator/provider/evolink.ts
Original file line number Diff line number Diff line change
@@ -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');
}
}
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down