Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ea6ff6997f | |||
| d0f2779471 |
@@ -18,6 +18,17 @@ describe('AiProviderService', () => {
|
|||||||
const body = JSON.parse(String(fetchMock.mock.calls[0][1].body));
|
const body = JSON.parse(String(fetchMock.mock.calls[0][1].body));
|
||||||
expect(body.messages[1]).toEqual({ role: 'user', content: [{ type: 'text', text: 'Inspect this screenshot' }, { type: 'image_url', image_url: { url: 'data:image/png;base64,aW1hZ2U=' } }] });
|
expect(body.messages[1]).toEqual({ role: 'user', content: [{ type: 'text', text: 'Inspect this screenshot' }, { type: 'image_url', image_url: { url: 'data:image/png;base64,aW1hZ2U=' } }] });
|
||||||
expect(body.response_format).toEqual({ type: 'json_object' });
|
expect(body.response_format).toEqual({ type: 'json_object' });
|
||||||
|
expect(body.enable_thinking).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(['qwen3.8-max', 'qwen3.8-flash', 'qwen3.7-plus'])('explicitly disables thinking for Qwen model %s', async model => {
|
||||||
|
const fetchMock = jest.fn().mockResolvedValue({ ok: true, json: async () => ({ choices: [{ message: { content: '{"ok":true}' } }] }) });
|
||||||
|
global.fetch = fetchMock as typeof fetch;
|
||||||
|
const service = new AiProviderService({ get: jest.fn() } as unknown as ConfigService);
|
||||||
|
await service.complete({ provider: 'QWEN', model, apiKey: 'fixture', system: 'Return JSON', json: true, messages: [{ role: 'user', content: 'Find the steering configuration' }] });
|
||||||
|
const body = JSON.parse(String(fetchMock.mock.calls[0][1].body));
|
||||||
|
expect(body).toMatchObject({ model, enable_thinking: false, response_format: { type: 'json_object' } });
|
||||||
|
expect(body.messages).toEqual([{ role: 'system', content: 'Return JSON' }, { role: 'user', content: 'Find the steering configuration' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('never silently drops an image on unsupported provider adapters', async () => {
|
it('never silently drops an image on unsupported provider adapters', async () => {
|
||||||
@@ -28,6 +39,31 @@ describe('AiProviderService', () => {
|
|||||||
expect(fetchMock).not.toHaveBeenCalled();
|
expect(fetchMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
'gpt-6.1-sol', 'gpt-6-astra', 'gpt-6-sol', 'gpt-6-luna', 'gpt-6.1-sol-2026-10-06',
|
||||||
|
'gpt-5', 'gpt-5-mini', 'gpt-5.1', 'gpt-5.5', 'gpt-5.6-sol', 'o1', 'o3', 'o4-mini',
|
||||||
|
])('requests low reasoning effort for OpenAI model %s', async model => {
|
||||||
|
const fetchMock = jest.fn().mockResolvedValue({ ok: true, json: async () => ({ choices: [{ message: { content: '{"ok":true}' } }] }) });
|
||||||
|
global.fetch = fetchMock as typeof fetch;
|
||||||
|
const service = new AiProviderService({ get: jest.fn() } as unknown as ConfigService);
|
||||||
|
await service.complete({ provider: 'OPENAI', model, apiKey: 'fixture', system: 'Return JSON', json: true, messages: [{ role: 'user', content: 'Find the steering configuration' }] });
|
||||||
|
const body = JSON.parse(String(fetchMock.mock.calls[0][1].body));
|
||||||
|
expect(body).toMatchObject({ model, reasoning_effort: 'low', response_format: { type: 'json_object' } });
|
||||||
|
expect(body).not.toHaveProperty('temperature');
|
||||||
|
expect(body.messages).toEqual([{ role: 'system', content: 'Return JSON' }, { role: 'user', content: 'Find the steering configuration' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
'gpt-4.1-mini', 'gpt-4o', 'custom-model', 'gpt-5-pro', 'gpt-5.2-pro-2025-12-11',
|
||||||
|
'gpt-5-chat-latest', 'gpt-5.1-chat-latest', 'o1-mini', 'o1-preview-2024-09-12', 'o3-deep-research',
|
||||||
|
])('preserves default reasoning settings for unsupported OpenAI variant %s', async model => {
|
||||||
|
const fetchMock = jest.fn().mockResolvedValue({ ok: true, json: async () => ({ choices: [{ message: { content: 'answer' } }] }) });
|
||||||
|
global.fetch = fetchMock as typeof fetch;
|
||||||
|
const service = new AiProviderService({ get: jest.fn() } as unknown as ConfigService);
|
||||||
|
await service.complete({ provider: 'OPENAI', model, apiKey: 'fixture', system: 'system', messages: [{ role: 'user', content: 'test' }] });
|
||||||
|
expect(JSON.parse(String(fetchMock.mock.calls[0][1].body))).not.toHaveProperty('reasoning_effort');
|
||||||
|
});
|
||||||
|
|
||||||
it('prefers the course Qwen endpoint over the server default', async () => {
|
it('prefers the course Qwen endpoint over the server default', async () => {
|
||||||
const fetchMock = jest.fn().mockResolvedValue({
|
const fetchMock = jest.fn().mockResolvedValue({
|
||||||
ok: true,
|
ok: true,
|
||||||
@@ -77,6 +113,8 @@ describe('AiProviderService', () => {
|
|||||||
const result = await service.complete({ provider, model, apiKey: 'fixture', system: 'Return JSON', messages: [{ role: 'user', content: 'test' }], json: true });
|
const result = await service.complete({ provider, model, apiKey: 'fixture', system: 'Return JSON', messages: [{ role: 'user', content: 'test' }], json: true });
|
||||||
const body = JSON.parse(String(fetchMock.mock.calls[0][1].body));
|
const body = JSON.parse(String(fetchMock.mock.calls[0][1].body));
|
||||||
expect(provider === 'GEMINI' ? body.generationConfig : body).not.toHaveProperty('temperature');
|
expect(provider === 'GEMINI' ? body.generationConfig : body).not.toHaveProperty('temperature');
|
||||||
|
expect(body).not.toHaveProperty('enable_thinking');
|
||||||
|
if (provider !== 'OPENAI') expect(body).not.toHaveProperty('reasoning_effort');
|
||||||
if (provider !== 'GEMINI') expect(body.model).toBe(model);
|
if (provider !== 'GEMINI') expect(body.model).toBe(model);
|
||||||
if (provider === 'OPENAI') expect(body.response_format).toEqual({ type: 'json_object' });
|
if (provider === 'OPENAI') expect(body.response_format).toEqual({ type: 'json_object' });
|
||||||
if (provider === 'GEMINI') expect(body.generationConfig.responseMimeType).toBe('application/json');
|
if (provider === 'GEMINI') expect(body.generationConfig.responseMimeType).toBe('application/json');
|
||||||
@@ -96,6 +134,8 @@ describe('AiProviderService', () => {
|
|||||||
await service.complete({ provider, model, apiKey: 'fixture', system: 'system', messages: [{ role: 'user', content: 'test' }] });
|
await service.complete({ provider, model, apiKey: 'fixture', system: 'system', messages: [{ role: 'user', content: 'test' }] });
|
||||||
const body = JSON.parse(String(fetchMock.mock.calls[0][1].body));
|
const body = JSON.parse(String(fetchMock.mock.calls[0][1].body));
|
||||||
expect(provider === 'GEMINI' ? body.generationConfig.temperature : body.temperature).toBe(0.2);
|
expect(provider === 'GEMINI' ? body.generationConfig.temperature : body.temperature).toBe(0.2);
|
||||||
|
expect(body).not.toHaveProperty('reasoning_effort');
|
||||||
|
if (provider !== 'QWEN') expect(body).not.toHaveProperty('enable_thinking');
|
||||||
if (provider !== 'GEMINI') expect(body.model).toBe(model);
|
if (provider !== 'GEMINI') expect(body.model).toBe(model);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -54,12 +54,14 @@ export class AiProviderService {
|
|||||||
|
|
||||||
private async openAi(input: CompletionInput): Promise<AiCompletionResult> {
|
private async openAi(input: CompletionInput): Promise<AiCompletionResult> {
|
||||||
const reasoningModel = /^gpt-(?:5|6)(?:[.-]|$)|^o[1-9](?:-|$)/.test(input.model);
|
const reasoningModel = /^gpt-(?:5|6)(?:[.-]|$)|^o[1-9](?:-|$)/.test(input.model);
|
||||||
|
const lowReasoningModel = reasoningModel && !/-(?:pro|chat|deep-research)(?:-|$)|^o1-(?:mini|preview)(?:-|$)/.test(input.model);
|
||||||
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { Authorization: `Bearer ${input.apiKey}`, 'Content-Type': 'application/json' },
|
headers: { Authorization: `Bearer ${input.apiKey}`, 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: input.model,
|
model: input.model,
|
||||||
temperature: reasoningModel ? undefined : 0.2,
|
temperature: reasoningModel ? undefined : 0.2,
|
||||||
|
reasoning_effort: lowReasoningModel ? 'low' : undefined,
|
||||||
response_format: input.json ? { type: 'json_object' } : undefined,
|
response_format: input.json ? { type: 'json_object' } : undefined,
|
||||||
messages: [{ role: 'system', content: input.system }, ...input.messages],
|
messages: [{ role: 'system', content: input.system }, ...input.messages],
|
||||||
}),
|
}),
|
||||||
@@ -146,6 +148,7 @@ export class AiProviderService {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: input.model,
|
model: input.model,
|
||||||
temperature: 0.2,
|
temperature: 0.2,
|
||||||
|
enable_thinking: false,
|
||||||
response_format: input.json ? { type: 'json_object' } : undefined,
|
response_format: input.json ? { type: 'json_object' } : undefined,
|
||||||
messages: [{ role: 'system', content: input.system }, ...input.messages.map(message => ({
|
messages: [{ role: 'system', content: input.system }, ...input.messages.map(message => ({
|
||||||
role: message.role,
|
role: message.role,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ describe('managed A-Copilot backend', () => {
|
|||||||
await provider.complete({ provider: 'A_COPILOT', model: 'ignored', apiKey: 'ignored', endpointUrl: 'https://untrusted.test', system: 'agent instructions', json: true, messages: [{ role: 'user', content: 'test' }] });
|
await provider.complete({ provider: 'A_COPILOT', model: 'ignored', apiKey: 'ignored', endpointUrl: 'https://untrusted.test', system: 'agent instructions', json: true, messages: [{ role: 'user', content: 'test' }] });
|
||||||
expect(fetchMock.mock.calls[0][0]).toBe('https://api.openai.com/v1/chat/completions');
|
expect(fetchMock.mock.calls[0][0]).toBe('https://api.openai.com/v1/chat/completions');
|
||||||
expect(fetchMock.mock.calls[0][1].headers.Authorization).toBe('Bearer shared-openai-key');
|
expect(fetchMock.mock.calls[0][1].headers.Authorization).toBe('Bearer shared-openai-key');
|
||||||
expect(JSON.parse(fetchMock.mock.calls[0][1].body)).toMatchObject({ model: 'gpt-6-astra', response_format: { type: 'json_object' }, messages: [{ role: 'system', content: 'agent instructions' }, { role: 'user', content: 'test' }] });
|
expect(JSON.parse(fetchMock.mock.calls[0][1].body)).toMatchObject({ model: 'gpt-6-astra', reasoning_effort: 'low', response_format: { type: 'json_object' }, messages: [{ role: 'system', content: 'agent instructions' }, { role: 'user', content: 'test' }] });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('never falls back to a different server provider when its configured key is missing', async () => {
|
it('never falls back to a different server provider when its configured key is missing', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user