-
Notifications
You must be signed in to change notification settings - Fork 3k
feat(dashscope): support DASHSCOPE_PROXY_BASE_URL for prompt cache via API gateway #3991
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1724132
7aa10d4
68e011e
79cb463
8144535
7d29bf5
669cd4a
68ef1f8
6524671
4696d34
420694b
e5bd8aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -22,6 +22,16 @@ import { DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES } from '../constants.js'; | |||||||||||||||||||||
| import { buildRuntimeFetchOptions } from '../../../utils/runtimeFetchOptions.js'; | ||||||||||||||||||||||
| import type { OpenAIRuntimeFetchOptions } from '../../../utils/runtimeFetchOptions.js'; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const mockDebugLogger = vi.hoisted(() => ({ | ||||||||||||||||||||||
| debug: vi.fn(), | ||||||||||||||||||||||
| info: vi.fn(), | ||||||||||||||||||||||
| warn: vi.fn(), | ||||||||||||||||||||||
| error: vi.fn(), | ||||||||||||||||||||||
| })); | ||||||||||||||||||||||
| vi.mock('../../../utils/debugLogger.js', () => ({ | ||||||||||||||||||||||
| createDebugLogger: vi.fn(() => mockDebugLogger), | ||||||||||||||||||||||
| })); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Mock OpenAI | ||||||||||||||||||||||
| vi.mock('openai', () => ({ | ||||||||||||||||||||||
| default: vi.fn().mockImplementation((config) => ({ | ||||||||||||||||||||||
|
|
@@ -38,13 +48,28 @@ vi.mock('../../../utils/runtimeFetchOptions.js', () => ({ | |||||||||||||||||||||
| buildRuntimeFetchOptions: vi.fn(), | ||||||||||||||||||||||
| })); | ||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] Test mock diverges from production and is fragile The
Suggested change
— glm-5.1 via Qwen Code /review
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic is already implemented at lines 60–62. Why is this recommendation showing up? |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Mock DASHSCOPE_PROXY_BASE_URL so tests can control its value | ||||||||||||||||||||||
| vi.mock('../constants.js', () => ({ | ||||||||||||||||||||||
| DEFAULT_TIMEOUT: 120000, | ||||||||||||||||||||||
| DEFAULT_MAX_RETRIES: 3, | ||||||||||||||||||||||
| DEFAULT_OPENAI_BASE_URL: 'https://api.openai.com/v1', | ||||||||||||||||||||||
| DEFAULT_DASHSCOPE_BASE_URL: | ||||||||||||||||||||||
| 'https://dashscope.aliyuncs.com/compatible-mode/v1', | ||||||||||||||||||||||
| DEFAULT_DEEPSEEK_BASE_URL: 'https://api.deepseek.com/v1', | ||||||||||||||||||||||
| DEFAULT_OPEN_ROUTER_BASE_URL: 'https://openrouter.ai/api/v1', | ||||||||||||||||||||||
| get DASHSCOPE_PROXY_BASE_URL() { | ||||||||||||||||||||||
| return process.env['DASHSCOPE_PROXY_BASE_URL']; | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| })); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| describe('DashScopeOpenAICompatibleProvider', () => { | ||||||||||||||||||||||
| let provider: DashScopeOpenAICompatibleProvider; | ||||||||||||||||||||||
| let mockContentGeneratorConfig: ContentGeneratorConfig; | ||||||||||||||||||||||
| let mockCliConfig: Config; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| beforeEach(() => { | ||||||||||||||||||||||
| vi.clearAllMocks(); | ||||||||||||||||||||||
| vi.unstubAllEnvs(); | ||||||||||||||||||||||
| const mockedBuildRuntimeFetchOptions = | ||||||||||||||||||||||
| buildRuntimeFetchOptions as unknown as MockedFunction< | ||||||||||||||||||||||
| (sdkType: 'openai', proxyUrl?: string) => OpenAIRuntimeFetchOptions | ||||||||||||||||||||||
|
|
@@ -162,6 +187,76 @@ describe('DashScopeOpenAICompatibleProvider', () => { | |||||||||||||||||||||
| expect(result).toBe(false); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| it('should return true when baseUrl matches DASHSCOPE_PROXY_BASE_URL', () => { | ||||||||||||||||||||||
| vi.stubEnv( | ||||||||||||||||||||||
| 'DASHSCOPE_PROXY_BASE_URL', | ||||||||||||||||||||||
| 'https://your-proxy.com/dashscope', | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const config = { | ||||||||||||||||||||||
| authType: AuthType.USE_OPENAI, | ||||||||||||||||||||||
| baseUrl: 'https://your-proxy.com/dashscope', | ||||||||||||||||||||||
| } as ContentGeneratorConfig; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const result = | ||||||||||||||||||||||
| DashScopeOpenAICompatibleProvider.isDashScopeProvider(config); | ||||||||||||||||||||||
| expect(result).toBe(true); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| it('should return false when baseUrl does not match DASHSCOPE_PROXY_BASE_URL', () => { | ||||||||||||||||||||||
| vi.stubEnv( | ||||||||||||||||||||||
| 'DASHSCOPE_PROXY_BASE_URL', | ||||||||||||||||||||||
| 'https://your-proxy.com/dashscope', | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const config = { | ||||||||||||||||||||||
| authType: AuthType.USE_OPENAI, | ||||||||||||||||||||||
| baseUrl: 'https://other-proxy.com/dashscope', | ||||||||||||||||||||||
| } as ContentGeneratorConfig; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const result = | ||||||||||||||||||||||
| DashScopeOpenAICompatibleProvider.isDashScopeProvider(config); | ||||||||||||||||||||||
| expect(result).toBe(false); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| it('should debug log when baseUrl does not match DASHSCOPE_PROXY_BASE_URL', () => { | ||||||||||||||||||||||
| vi.stubEnv( | ||||||||||||||||||||||
| 'DASHSCOPE_PROXY_BASE_URL', | ||||||||||||||||||||||
| 'https://your-proxy.com/dashscope', | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const config = { | ||||||||||||||||||||||
| authType: AuthType.USE_OPENAI, | ||||||||||||||||||||||
| baseUrl: 'https://other-proxy.com/dashscope', | ||||||||||||||||||||||
| } as ContentGeneratorConfig; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const result = | ||||||||||||||||||||||
| DashScopeOpenAICompatibleProvider.isDashScopeProvider(config); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| expect(result).toBe(false); | ||||||||||||||||||||||
| expect(mockDebugLogger.debug).toHaveBeenCalledWith( | ||||||||||||||||||||||
| expect.stringContaining( | ||||||||||||||||||||||
| 'DASHSCOPE_PROXY_BASE_URL is configured but the request baseUrl does not match', | ||||||||||||||||||||||
| ), | ||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] Test assertion does not match production code — the test will fail. The test expects the debug log to contain The
Suggested change
— DeepSeek/deepseek-v4-pro via Qwen Code /review |
||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| it('should return true when baseUrl matches DASHSCOPE_PROXY_BASE_URL with trailing slash', () => { | ||||||||||||||||||||||
| vi.stubEnv( | ||||||||||||||||||||||
| 'DASHSCOPE_PROXY_BASE_URL', | ||||||||||||||||||||||
| 'https://your-proxy.com/dashscope', | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const config = { | ||||||||||||||||||||||
| authType: AuthType.USE_OPENAI, | ||||||||||||||||||||||
| baseUrl: 'https://your-proxy.com/dashscope/', | ||||||||||||||||||||||
| } as ContentGeneratorConfig; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const result = | ||||||||||||||||||||||
| DashScopeOpenAICompatibleProvider.isDashScopeProvider(config); | ||||||||||||||||||||||
| expect(result).toBe(true); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| describe('buildHeaders', () => { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |||||||||||||||||||||||||||||
| DEFAULT_TIMEOUT, | ||||||||||||||||||||||||||||||
| DEFAULT_MAX_RETRIES, | ||||||||||||||||||||||||||||||
| DEFAULT_DASHSCOPE_BASE_URL, | ||||||||||||||||||||||||||||||
| DASHSCOPE_PROXY_BASE_URL, | ||||||||||||||||||||||||||||||
| } from '../constants.js'; | ||||||||||||||||||||||||||||||
| import type { | ||||||||||||||||||||||||||||||
| DashScopeRequestMetadata, | ||||||||||||||||||||||||||||||
|
|
@@ -15,8 +16,11 @@ | |||||||||||||||||||||||||||||
| ChatCompletionToolWithCache, | ||||||||||||||||||||||||||||||
| } from './types.js'; | ||||||||||||||||||||||||||||||
| import { buildRuntimeFetchOptions } from '../../../utils/runtimeFetchOptions.js'; | ||||||||||||||||||||||||||||||
| import { createDebugLogger } from '../../../utils/debugLogger.js'; | ||||||||||||||||||||||||||||||
| import { DefaultOpenAICompatibleProvider } from './default.js'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const debugLogger = createDebugLogger('DashScopeOpenAICompatibleProvider'); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export class DashScopeOpenAICompatibleProvider extends DefaultOpenAICompatibleProvider { | ||||||||||||||||||||||||||||||
| constructor( | ||||||||||||||||||||||||||||||
| contentGeneratorConfig: ContentGeneratorConfig, | ||||||||||||||||||||||||||||||
|
|
@@ -33,8 +37,31 @@ | |||||||||||||||||||||||||||||
| if (authType === AuthType.QWEN_OAUTH) return true; | ||||||||||||||||||||||||||||||
| if (!baseUrl) return true; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const normalizedBaseUrl = baseUrl.endsWith('/') | ||||||||||||||||||||||||||||||
| ? baseUrl.slice(0, -1) | ||||||||||||||||||||||||||||||
| : baseUrl; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Matches: dashscope.aliyuncs.com, *.dashscope.aliyuncs.com, or *.dashscope-intl.aliyuncs.com | ||||||||||||||||||||||||||||||
| return /([\w-]+\.)?dashscope(-intl)?\.aliyuncs\.com/i.test(baseUrl); | ||||||||||||||||||||||||||||||
| const isDashscopeOrigin = | ||||||||||||||||||||||||||||||
| /([\w-]+\.)?dashscope(-intl)?\.aliyuncs\.com/i.test(normalizedBaseUrl); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Check if proxy is configured and matches | ||||||||||||||||||||||||||||||
| const normalizedProxyUrl = DASHSCOPE_PROXY_BASE_URL?.endsWith('/') | ||||||||||||||||||||||||||||||
| ? DASHSCOPE_PROXY_BASE_URL.slice(0, -1) | ||||||||||||||||||||||||||||||
| : DASHSCOPE_PROXY_BASE_URL; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] Case-sensitivity inconsistency between origin check and proxy check The origin-domain regex on line 43 uses the
Suggested change
— glm-5.1 via Qwen Code /review |
||||||||||||||||||||||||||||||
| const isProxyMatch = Boolean( | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] Proxy URL comparison uses exact equality — may miss path prefix mismatches The proxy match uses The origin-domain check on line 46 uses a lenient regex substring match, so it's immune to this. The asymmetry is intentional but the strict proxy comparison could cause real misconfiguration issues.
Suggested change
— glm-5.1 via Qwen Code /review |
||||||||||||||||||||||||||||||
| normalizedProxyUrl && | ||||||||||||||||||||||||||||||
| normalizedBaseUrl.toLowerCase() === normalizedProxyUrl.toLowerCase(), | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (normalizedProxyUrl && !isDashscopeOrigin && !isProxyMatch) { | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] The codebase has 100+ usages of Move it to module level for consistency and to avoid unnecessary allocations.
Suggested change
— DeepSeek/deepseek-v4-pro via Qwen Code /review |
||||||||||||||||||||||||||||||
| debugLogger.debug( | ||||||||||||||||||||||||||||||
| `DASHSCOPE_PROXY_BASE_URL is configured but the request baseUrl does not match. DashScope headers/cache control will be skipped.`, | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] Debug log on proxy mismatch does not include the actual URL values being compared. When
Suggested change
— DeepSeek/deepseek-v4-pro via Qwen Code /review
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove the url in logs to prevent URL exposure. |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return isDashscopeOrigin || isProxyMatch; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| override buildHeaders(): Record<string, string | undefined> { | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.