feat: use managed model capabilities for reasoning and image input
This commit is contained in:
@@ -28,6 +28,7 @@ import type {
|
||||
PiRpcResponse,
|
||||
} from '../../electron/coding-runtime/pi/rpc-client';
|
||||
import { PI_084_TEXT_TURN } from '../fixtures/pi-0.84.2-projector-fixtures';
|
||||
import { unknownManagedModelCapability, type ManagedModelRequest } from '../../shared/managed-model-capabilities';
|
||||
|
||||
const roots: string[] = [];
|
||||
const NOW = '2026-08-22T15:00:00.000Z';
|
||||
@@ -152,9 +153,11 @@ class RuntimeFakeWorker implements PiConversationWorker {
|
||||
}
|
||||
|
||||
class TrackingExtensionHost extends PiManagedExtensionHost {
|
||||
managedRequest?: ManagedModelRequest;
|
||||
readonly runs = new Map<string, { generation: number; runId: string }>();
|
||||
|
||||
override async bindRun(conversationId: string, generation: number, runId: string): Promise<void> {
|
||||
override async bindRun(conversationId: string, generation: number, runId: string, request?: ManagedModelRequest): Promise<void> {
|
||||
this.managedRequest = request;
|
||||
this.runs.set(conversationId, { generation, runId });
|
||||
}
|
||||
|
||||
@@ -168,6 +171,57 @@ afterEach(async () => {
|
||||
});
|
||||
|
||||
describe('Pi Conversation runtime', () => {
|
||||
it('persists native managed choices and rejects removed effort or unsupported images before prompt', async () => {
|
||||
const projectPath = await mkdtemp(path.join(tmpdir(), 'makelore-managed-runtime-'));
|
||||
roots.push(projectPath);
|
||||
const projectStore = createCodingProjectStore(createMemoryCodingProjectStorage(), {
|
||||
createId: () => 'project-managed', now: () => NOW,
|
||||
});
|
||||
await createLocalCodingProject({ projectPath, now: NOW }, projectStore);
|
||||
const model = { accountId: 'niancode-user-models', modelId: 'unfamiliar-model', thinkingLevel: 'high' as const };
|
||||
await createCodingProjectAgent(projectPath, {
|
||||
id: 'agent-a', avatarId: 'avatar-01', roleName: 'Implementer', name: 'Agent A',
|
||||
model, modelResolution: 'resolved',
|
||||
responsibility: { mission: 'Implement', owns: [], boundaries: [], collaborators: [], principles: [] },
|
||||
}, { now: NOW });
|
||||
const store = createCodingConversationStore(projectPath);
|
||||
const conversation = await store.create({ agentId: 'agent-a', title: 'Managed', model, modelResolution: 'resolved' });
|
||||
let worker: RuntimeFakeWorker;
|
||||
const pool = new PiWorkerPool({ openWorker: async ({generation}) => {
|
||||
worker = new RuntimeFakeWorker('managed-worker', generation);
|
||||
return { worker, session: { piSessionId: 'session', sessionKey: 'key' } };
|
||||
} });
|
||||
const capability = unknownManagedModelCapability();
|
||||
capability.inputModalities = ['text'];
|
||||
capability.reasoning = { supported: true, canDisable: true, defaultEnabled: true,
|
||||
controlFormat: 'qwen', effortValues: ['xhigh', 'future-native'], defaultEffort: 'xhigh', budget: null };
|
||||
const host = new TrackingExtensionHost();
|
||||
const runtime = new PiConversationRuntime({
|
||||
pool, registry: new PiSessionRegistry({ projectStore }), extensionHost: host,
|
||||
resolveImages: async () => [],
|
||||
resolveModel: async candidate => ({ ...candidate, input: ['text'],
|
||||
runtimeProviderId: 'managed', managedCapability: structuredClone(capability) }),
|
||||
});
|
||||
try {
|
||||
await runtime.prepare({ conversationId: conversation.id, projectId: 'project-managed', agentId: 'agent-a',
|
||||
title: conversation.title, model: { model, modelResolution: 'resolved' } });
|
||||
expect((await runtime.getSnapshot(conversation.id)).conversation.model.model?.reasoningChoice).toEqual({ mode: 'default' });
|
||||
await runtime.setThinking({ conversationId: conversation.id, thinkingLevel: 'off',
|
||||
reasoningChoice: { mode: 'enabled', effort: 'future-native' } });
|
||||
expect((await store.get(conversation.id))?.model?.reasoningChoice).toEqual({ mode: 'enabled', effort: 'future-native' });
|
||||
capability.reasoning.effortValues = ['xhigh'];
|
||||
const prompt = { conversationId: conversation.id, clientRequestId: 'request-managed', mode: 'prompt' as const, text: 'Hello', attachments: [] };
|
||||
await expect(runtime.prompt(prompt)).rejects.toThrow('思考选项已不可用');
|
||||
expect(worker!.requests.filter(r => r.type === 'prompt')).toHaveLength(0);
|
||||
await runtime.setThinking({ conversationId: conversation.id, thinkingLevel: 'off', reasoningChoice: { mode: 'default' } });
|
||||
await expect(runtime.prompt({ ...prompt, attachments: [{ attachmentId: 'image' }] })).rejects.toThrow('图片输入');
|
||||
await runtime.prompt(prompt);
|
||||
expect(host.managedRequest).toEqual({ modelId: 'unfamiliar-model', choice: { mode: 'default' }, reasoningFields: {} });
|
||||
for (const event of PI_084_TEXT_TURN.events) worker!.emit(structuredClone(event));
|
||||
await runtime.getSnapshot(conversation.id);
|
||||
} finally { await runtime.shutdown(); }
|
||||
});
|
||||
|
||||
it('separates RPC acceptance from settle and changes only the target Conversation model', async () => {
|
||||
const projectPath = await mkdtemp(path.join(tmpdir(), 'makelore-pi-runtime-'));
|
||||
roots.push(projectPath);
|
||||
|
||||
Reference in New Issue
Block a user