feat: use managed model capabilities for reasoning and image input

This commit is contained in:
brother7 committed 2026-09-12 21:37:18 +08:00
1 parent e7701d1f2c
commit 26cbb29aa9
29 files changed
+652 -271

No files matched your search

+31 -210
View File
@@ -1,3 +1,4 @@
import { normalizeManagedModelCatalog } from '../../shared/managed-model-capabilities';
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
@@ -107,23 +108,10 @@ describe('Pi Provider catalog', () => {
'X-Works-Square-AI-Token',
]);
expect(descriptor.models[0]).toMatchObject({
id: 'qwen3.6-plus',
input: ['text', 'image'],
reasoning: true,
contextWindow: 1_000_000,
maxOutputTokens: 65_536,
thinkingLevelMap: {
minimal: null,
low: null,
medium: null,
high: 'high',
},
compat: {
thinkingFormat: 'qwen',
supportsDeveloperRole: false,
supportsReasoningEffort: false,
supportsStore: false,
},
id: 'qwen3.6-plus', input: ['text'], reasoning: false,
contextWindow: 1_000_000, maxOutputTokens: 65_536,
managedCapability: { resolutionStatus: 'unknown' },
compat: { supportsDeveloperRole: false, supportsReasoningEffort: false, supportsStore: false },
});
expect(catalog.modelsFile.providers[descriptor.runtimeProviderId]?.models[0]?.compat)
.toMatchObject({ supportsDeveloperRole: false });
@@ -170,48 +158,6 @@ describe('Pi Provider catalog', () => {
expect(descriptor.models[0]?.compat?.supportsDeveloperRole).toBe(false);
});
it('projects Qwen3.8 Max reasoning effort levels into Pi models.json', () => {
const provider = account({
id: 'niancode-user-models',
vendorId: 'custom',
apiProtocol: 'openai-completions',
baseUrl: 'http://127.0.0.1:54321/api/ai-proxy/v1',
model: 'qwen3.8-max',
metadata: {
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
customModels: ['qwen3.8-max'],
},
});
const catalog = buildPiProviderCatalog({ accounts: [provider] });
const descriptor = catalog.descriptors[0]!.models[0]!;
const written = catalog.modelsFile.providers[resolvePiRuntimeProviderId(provider.id)]!.models[0]!;
expect(descriptor).toMatchObject({
id: 'qwen3.8-max',
input: ['text', 'image'],
reasoning: true,
contextWindow: 1_000_000,
maxOutputTokens: 131_072,
thinkingLevelMap: {
minimal: null,
low: 'low',
medium: 'medium',
high: 'xhigh',
},
compat: {
thinkingFormat: 'qwen',
supportsDeveloperRole: false,
supportsReasoningEffort: true,
supportsStore: false,
},
});
expect(written).toMatchObject({
reasoning: true,
thinkingLevelMap: descriptor.thinkingLevelMap,
compat: descriptor.compat,
});
});
it('uses account-scoped model capability metadata and rejects unavailable models', () => {
const provider = account({ id: 'account-with-vision', model: 'vision-model' });
@@ -244,165 +190,40 @@ describe('Pi Provider catalog', () => {
})).toThrowError(PiProviderConfigError);
});
it('projects the managed DeepSeek capability contract into Pi models.json', () => {
it.each(['qwen', 'deepseek'] as const)('uses v2 %s capabilities for unfamiliar models and native effort values', format => {
const provider = account({
id: 'niancode-user-models',
vendorId: 'custom',
apiProtocol: 'openai-completions',
baseUrl: 'http://127.0.0.1:54321/api/ai-proxy/v1',
model: 'deepseek/deepseek-v4-pro',
id: 'niancode-user-models', vendorId: 'custom', apiProtocol: 'openai-completions',
model: 'future-model', baseUrl: 'https://gateway.test/v1',
metadata: {
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
customModels: ['deepseek/deepseek-v4-pro'],
worksSquareModelCapabilitiesV2: normalizeManagedModelCatalog({
schema_version: 2, models: { 'future-model': {
input_modalities: ['text', 'image'], output_modalities: ['text'],
reasoning: { supported: true, can_disable: true, default_enabled: true,
control_format: format, effort_values: ['low', 'medium', 'xhigh', 'max', 'future'] },
limits: { context_window: 123456, max_output_tokens: 1234 }, resolution_status: 'ready',
} },
}),
},
});
const catalog = buildPiProviderCatalog({ accounts: [provider] });
const descriptor = catalog.descriptors[0]!.models[0]!;
const written = catalog.modelsFile.providers[resolvePiRuntimeProviderId(provider.id)]!.models[0]!;
expect(descriptor).toMatchObject({
id: 'deepseek-v4-pro',
reasoning: true,
contextWindow: 1_000_000,
maxOutputTokens: 384_000,
compat: {
thinkingFormat: 'deepseek',
requiresReasoningContentOnAssistantMessages: true,
},
thinkingLevelMap: {
minimal: null,
low: 'low',
medium: null,
high: 'high',
max: 'max',
},
});
expect(written).toMatchObject({
reasoning: true,
thinkingLevelMap: descriptor.thinkingLevelMap,
compat: descriptor.compat,
});
});
it('uses server reasoning capabilities while retaining local model metadata', () => {
const provider = account({
id: 'niancode-user-models',
vendorId: 'custom',
apiProtocol: 'openai-completions',
baseUrl: 'http://127.0.0.1:54321/api/ai-proxy/v1',
model: 'qwen3.8-max',
metadata: {
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
customModels: ['qwen3.8-max'],
worksSquareModelCapabilities: {
'qwen3.8-max': {
reasoningEfforts: ['low'],
reasoningCanDisable: false,
},
},
},
});
const descriptor = buildPiProviderCatalog({ accounts: [provider] }).descriptors[0]!.models[0]!;
expect(descriptor).toMatchObject({
id: 'qwen3.8-max',
input: ['text', 'image'],
reasoning: true,
contextWindow: 1_000_000,
maxOutputTokens: 131_072,
thinkingLevelMap: {
off: null,
minimal: null,
low: 'low',
medium: null,
high: null,
max: null,
},
compat: {
thinkingFormat: 'qwen',
supportsDeveloperRole: false,
supportsReasoningEffort: true,
supportsStore: false,
},
});
});
it('enables reasoning-effort serialization for a server model without a local profile', () => {
const provider = account({
id: 'niancode-user-models',
vendorId: 'custom',
apiProtocol: 'openai-completions',
baseUrl: 'http://127.0.0.1:54321/api/ai-proxy/v1',
model: 'future-reasoning-model',
metadata: {
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
customModels: ['future-reasoning-model'],
worksSquareModelCapabilities: {
'future-reasoning-model': {
reasoningEfforts: ['low', 'high', 'max'],
reasoningCanDisable: true,
},
},
},
});
const descriptor = buildPiProviderCatalog({ accounts: [provider] }).descriptors[0]!.models[0]!;
expect(descriptor).toMatchObject({
id: 'future-reasoning-model',
reasoning: true,
compat: {
supportsDeveloperRole: false,
supportsReasoningEffort: true,
},
thinkingLevelMap: {
minimal: null,
low: 'low',
medium: null,
high: 'high',
max: 'max',
},
});
expect(descriptor.thinkingLevelMap).not.toHaveProperty('off');
});
it('lets an explicit empty server effort list override a locally reasoning model', () => {
const provider = account({
id: 'niancode-user-models',
vendorId: 'custom',
apiProtocol: 'openai-completions',
baseUrl: 'http://127.0.0.1:54321/api/ai-proxy/v1',
model: 'deepseek-v4-pro',
metadata: {
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
customModels: ['deepseek-v4-pro'],
worksSquareModelCapabilities: {
'deepseek-v4-pro': {
reasoningEfforts: [],
reasoningCanDisable: false,
},
},
},
});
const descriptor = buildPiProviderCatalog({ accounts: [provider] }).descriptors[0]!.models[0]!;
expect(descriptor.reasoning).toBe(false);
expect(descriptor.thinkingLevelMap).toEqual({
off: null,
minimal: null,
low: null,
medium: null,
high: null,
max: null,
});
expect(descriptor.compat).toMatchObject({
thinkingFormat: 'deepseek',
supportsReasoningEffort: true,
requiresReasoningContentOnAssistantMessages: true,
input: ['text', 'image'], reasoning: true, contextWindow: 123456, maxOutputTokens: 1234,
compat: { thinkingFormat: format, supportsReasoningEffort: false, requiresReasoningContentOnAssistantMessages: true },
managedCapability: { reasoning: { effortValues: ['low', 'medium', 'xhigh', 'max', 'future'] } },
});
expect(descriptor.thinkingLevelMap).toBeUndefined();
expect(catalog.modelsFile.providers[resolvePiRuntimeProviderId(provider.id)]!.models[0])
.not.toHaveProperty('managedCapability');
const profile = provider.metadata!.worksSquareModelCapabilitiesV2!.models['future-model']!;
profile.inputModalities = ['text'];
profile.reasoning.supported = false;
expect(buildPiProviderCatalog({ accounts: [provider] }).descriptors[0]!.models[0])
.toMatchObject({ input: ['text'], reasoning: false });
});
it('serializes DeepSeek off without sending a reasoning effort', async () => {