diff --git a/src/server/__tests__/providers.test.ts b/src/server/__tests__/providers.test.ts index 6ed3ecae..7381db61 100644 --- a/src/server/__tests__/providers.test.ts +++ b/src/server/__tests__/providers.test.ts @@ -1054,6 +1054,47 @@ describe('ProviderService', () => { globalThis.fetch = originalFetch } }) + + test('normalizes context-window suffixes before forwarding OpenAI Chat proxy requests', async () => { + const originalFetch = globalThis.fetch + const calls: Array<{ body: Record }> = [] + globalThis.fetch = mock(async (_url: string | URL | Request, init?: RequestInit) => { + calls.push({ body: JSON.parse(String(init?.body)) as Record }) + return new Response(JSON.stringify({ + id: 'chatcmpl-1', + object: 'chat.completion', + created: 0, + model: 'mimo-v2.5-pro', + choices: [{ index: 0, message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }) + }) as typeof fetch + + try { + const svc = new ProviderService() + const provider = await svc.addProvider(sampleInput({ apiFormat: 'openai_chat' })) + await svc.activateProvider(provider.id) + + const req = new Request('http://localhost:3456/proxy/v1/messages', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + model: 'mimo-v2.5-pro[1m]', + max_tokens: 64, + messages: [{ role: 'user', content: 'hello from proxy' }], + }), + }) + + const res = await handleProxyRequest(req, new URL(req.url)) + expect(res.status).toBe(200) + expect(calls[0].body.model).toBe('mimo-v2.5-pro') + } finally { + globalThis.fetch = originalFetch + } + }) }) describe('testProvider', () => { @@ -1151,6 +1192,77 @@ describe('ProviderService', () => { } }) + test('normalizes context-window suffixes for Anthropic-compatible connectivity tests', async () => { + const originalFetch = globalThis.fetch + const calls: Array<{ body: Record }> = [] + globalThis.fetch = mock(async (_url: string | URL | Request, init?: RequestInit) => { + calls.push({ body: JSON.parse(String(init?.body)) as Record }) + return new Response(JSON.stringify({ + type: 'message', + model: 'mimo-v2.5-pro', + content: [], + }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }) + }) as typeof fetch + + try { + const svc = new ProviderService() + const result = await svc.testProviderConfig({ + baseUrl: 'https://api.xiaomimimo.com/anthropic', + apiKey: 'sk-api', + modelId: 'mimo-v2.5-pro[1m]', + authStrategy: 'auth_token', + apiFormat: 'anthropic', + }) + + expect(result.connectivity.success).toBe(true) + expect(result.connectivity.modelUsed).toBe('mimo-v2.5-pro') + expect(calls[0].body.model).toBe('mimo-v2.5-pro') + } finally { + globalThis.fetch = originalFetch + } + }) + + test('normalizes context-window suffixes for provider proxy pipeline tests', async () => { + const originalFetch = globalThis.fetch + const calls: Array<{ body: Record }> = [] + globalThis.fetch = mock(async (_url: string | URL | Request, init?: RequestInit) => { + calls.push({ body: JSON.parse(String(init?.body)) as Record }) + return new Response(JSON.stringify({ + id: 'chatcmpl-1', + object: 'chat.completion', + created: 0, + model: 'mimo-v2.5-pro', + choices: [{ index: 0, message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }) + }) as typeof fetch + + try { + const svc = new ProviderService() + const result = await svc.testProviderConfig({ + baseUrl: 'https://api.example.com', + apiKey: 'sk-api', + modelId: 'mimo-v2.5-pro[1m]', + authStrategy: 'api_key', + apiFormat: 'openai_chat', + }) + + expect(result.connectivity.success).toBe(true) + expect(result.proxy?.success).toBe(true) + expect(result.connectivity.modelUsed).toBe('mimo-v2.5-pro') + expect(result.proxy?.modelUsed).toBe('mimo-v2.5-pro') + expect(calls.map((call) => call.body.model)).toEqual(['mimo-v2.5-pro', 'mimo-v2.5-pro']) + } finally { + globalThis.fetch = originalFetch + } + }) + test('should use configured network timeout for provider tests', async () => { await fs.writeFile( path.join(tmpDir, 'settings.json'), diff --git a/src/server/proxy/handler.ts b/src/server/proxy/handler.ts index 5a8112de..2a1583ac 100644 --- a/src/server/proxy/handler.ts +++ b/src/server/proxy/handler.ts @@ -21,6 +21,7 @@ import { openaiResponsesStreamToAnthropic } from './streaming/openaiResponsesStr import type { AnthropicRequest } from './transform/types.js' import { getProxyFetchOptions } from '../../utils/proxy.js' import { getManualNetworkProxyUrl, loadNetworkSettings } from '../services/networkSettings.js' +import { normalizeModelStringForAPI } from '../../utils/model/model.js' const providerService = new ProviderService() @@ -127,7 +128,10 @@ export async function handleProxyRequest(req: Request, url: URL): Promise