feat: consume server model reasoning capabilities
This commit is contained in:
@@ -1,20 +1,30 @@
|
||||
import { EventEmitter } from 'node:events';
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { handleProviderRoutes } from '@electron/api/routes/providers';
|
||||
import {
|
||||
handleProviderRoutes,
|
||||
importCurrentUserModelConfig,
|
||||
normalizeImportedUserModelConfig,
|
||||
} from '@electron/api/routes/providers';
|
||||
import type { ProviderAccount } from '@electron/shared/providers/types';
|
||||
|
||||
const providerServiceMock = vi.hoisted(() => ({
|
||||
getAccount: vi.fn(),
|
||||
getAccountApiKey: vi.fn(),
|
||||
getDefaultAccountId: vi.fn(),
|
||||
updateAccount: vi.fn(),
|
||||
setDefaultAccount: vi.fn(),
|
||||
}));
|
||||
const proxyAwareFetchMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock('@electron/services/providers/provider-service', () => ({
|
||||
getProviderService: () => providerServiceMock,
|
||||
}));
|
||||
|
||||
vi.mock('@electron/utils/proxy-fetch', () => ({
|
||||
proxyAwareFetch: (...args: unknown[]) => proxyAwareFetchMock(...args),
|
||||
}));
|
||||
|
||||
function createRequest(method: string, body?: unknown): IncomingMessage {
|
||||
const request = new EventEmitter();
|
||||
Object.assign(request, {
|
||||
@@ -66,6 +76,7 @@ describe('provider host api routes', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
providerServiceMock.getAccount.mockResolvedValue(account());
|
||||
providerServiceMock.getAccountApiKey.mockResolvedValue('stored-key');
|
||||
providerServiceMock.updateAccount.mockResolvedValue(account({
|
||||
updatedAt: '2026-08-24T01:00:00.000Z',
|
||||
}));
|
||||
@@ -73,6 +84,139 @@ describe('provider host api routes', () => {
|
||||
providerServiceMock.setDefaultAccount.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it('normalizes optional Works model capabilities before storing the account', () => {
|
||||
expect(normalizeImportedUserModelConfig({
|
||||
label: 'Makelore Models',
|
||||
base_url: 'https://gateway.test/v1',
|
||||
api_key: 'gateway-key',
|
||||
credential_mode: 'works_square_ai_gateway',
|
||||
models: ['deepseek/deepseek-v4-pro', 'qwen3.8-max'],
|
||||
model_capabilities: {
|
||||
'deepseek/deepseek-v4-pro': {
|
||||
reasoning_efforts: ['max', 'low', 'medium', 'low'],
|
||||
reasoning_can_disable: true,
|
||||
},
|
||||
'qwen3.8-max': {
|
||||
reasoning_efforts: ['high'],
|
||||
reasoning_can_disable: false,
|
||||
},
|
||||
'not-provisioned': {
|
||||
reasoning_efforts: ['low'],
|
||||
reasoning_can_disable: true,
|
||||
},
|
||||
},
|
||||
})).toMatchObject({
|
||||
models: ['deepseek-v4-pro', 'qwen3.8-max'],
|
||||
modelCapabilities: {
|
||||
'deepseek-v4-pro': {
|
||||
reasoningEfforts: ['low', 'max'],
|
||||
reasoningCanDisable: true,
|
||||
},
|
||||
'qwen3.8-max': {
|
||||
reasoningEfforts: ['high'],
|
||||
reasoningCanDisable: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('persists server capabilities and invalidates the runtime when they change', async () => {
|
||||
const existing = account({
|
||||
id: 'niancode-user-models',
|
||||
vendorId: 'custom',
|
||||
model: 'deepseek-v4-pro',
|
||||
metadata: {
|
||||
customModels: ['deepseek-v4-pro'],
|
||||
worksSquareModelCapabilities: {
|
||||
'deepseek-v4-pro': {
|
||||
reasoningEfforts: ['high'],
|
||||
reasoningCanDisable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
providerServiceMock.getAccount.mockResolvedValue(existing);
|
||||
providerServiceMock.updateAccount.mockImplementation(async (_accountId: string, patch: Partial<ProviderAccount>) => ({
|
||||
...existing,
|
||||
...patch,
|
||||
}));
|
||||
proxyAwareFetchMock.mockResolvedValue(new Response(JSON.stringify({
|
||||
label: 'Makelore Models',
|
||||
base_url: 'https://gateway.test/v1',
|
||||
api_key: 'gateway-key',
|
||||
credential_mode: 'api_key',
|
||||
models: ['deepseek/deepseek-v4-pro'],
|
||||
model_capabilities: {
|
||||
'deepseek/deepseek-v4-pro': {
|
||||
reasoning_efforts: ['low', 'high', 'max'],
|
||||
reasoning_can_disable: true,
|
||||
},
|
||||
},
|
||||
}), { status: 200, headers: { 'content-type': 'application/json' } }));
|
||||
|
||||
const imported = await importCurrentUserModelConfig(context, 'access-token');
|
||||
|
||||
expect(imported.account.metadata?.worksSquareModelCapabilities).toEqual({
|
||||
'deepseek-v4-pro': {
|
||||
reasoningEfforts: ['low', 'high', 'max'],
|
||||
reasoningCanDisable: true,
|
||||
},
|
||||
});
|
||||
expect(providerServiceMock.updateAccount).toHaveBeenCalledWith(
|
||||
'niancode-user-models',
|
||||
expect.objectContaining({
|
||||
metadata: expect.objectContaining({
|
||||
worksSquareModelCapabilities: {
|
||||
'deepseek-v4-pro': {
|
||||
reasoningEfforts: ['low', 'high', 'max'],
|
||||
reasoningCanDisable: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
'gateway-key',
|
||||
);
|
||||
expect(markProviderStale).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('clears a previous server capability override when the optional field is absent', async () => {
|
||||
const existing = account({
|
||||
id: 'niancode-user-models',
|
||||
vendorId: 'custom',
|
||||
model: 'deepseek-v4-pro',
|
||||
metadata: {
|
||||
customModels: ['deepseek-v4-pro'],
|
||||
worksSquareModelCapabilities: {
|
||||
'deepseek-v4-pro': {
|
||||
reasoningEfforts: ['max'],
|
||||
reasoningCanDisable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
providerServiceMock.getAccount.mockResolvedValue(existing);
|
||||
providerServiceMock.updateAccount.mockImplementation(async (_accountId: string, patch: Partial<ProviderAccount>) => ({
|
||||
...existing,
|
||||
...patch,
|
||||
}));
|
||||
proxyAwareFetchMock.mockResolvedValue(new Response(JSON.stringify({
|
||||
base_url: 'https://gateway.test/v1',
|
||||
api_key: 'gateway-key',
|
||||
models: ['deepseek/deepseek-v4-pro'],
|
||||
}), { status: 200, headers: { 'content-type': 'application/json' } }));
|
||||
|
||||
const imported = await importCurrentUserModelConfig(context, 'access-token');
|
||||
|
||||
expect(imported.account.metadata).not.toHaveProperty('worksSquareModelCapabilities');
|
||||
expect(providerServiceMock.updateAccount).toHaveBeenCalledWith(
|
||||
'niancode-user-models',
|
||||
expect.objectContaining({
|
||||
metadata: expect.not.objectContaining({ worksSquareModelCapabilities: expect.anything() }),
|
||||
}),
|
||||
'gateway-key',
|
||||
);
|
||||
});
|
||||
|
||||
it('marks Pi provider input stale after an account credential update', async () => {
|
||||
const result = createResponse();
|
||||
const handled = await handleProviderRoutes(
|
||||
|
||||
Reference in New Issue
Block a user