feat: use managed model capabilities for reasoning and image input

This commit is contained in:
2026-09-12 21:37:18 +08:00
parent e7701d1f2c
commit 26cbb29aa9
29 changed files with 652 additions and 271 deletions

View File

@@ -98,6 +98,7 @@ import {
} from './extension-ui-projector';
type RuntimeIdKind = 'run' | 'queue';
import { buildManagedModelRequest, managedPiThinkingLevel, validateManagedReasoningChoice } from '../../../shared/managed-model-capabilities';
export interface PiConversationRuntimeOptions {
pool: PiWorkerPool;
@@ -306,6 +307,9 @@ export function createPiManagedWorkerOpener(
let extension;
try {
extension = await options.extensionHost.registerWorker({
...(selection.managedCapability ? { managedModelRequest: buildManagedModelRequest(
model.modelId, model.reasoningChoice ?? { mode: 'default' }, selection.managedCapability,
) } : {}),
conversationId: input.conversation.conversationId,
generation: input.generation,
projectId: input.conversation.projectId,
@@ -796,6 +800,7 @@ export class PiConversationRuntime implements CodingConversationRuntime {
}
this.snapshot(input.conversationId);
const managedRequest = await this.prepareManagedRequest(input.conversationId, input.attachments.length > 0);
const images = await this.resolveImages(input.attachments);
const runId = this.id('run');
this.acquireRunBackgroundLease(input.conversationId, runId);
@@ -823,7 +828,7 @@ export class PiConversationRuntime implements CodingConversationRuntime {
let ticket;
try {
if (this.extensionHost && generation) {
await this.extensionHost.bindRun(input.conversationId, generation, runId);
await this.extensionHost.bindRun(input.conversationId, generation, runId, managedRequest);
}
ticket = this.pool.startTopLevel({
conversationId: input.conversationId,
@@ -902,10 +907,12 @@ export class PiConversationRuntime implements CodingConversationRuntime {
async validateModel(model: ProductModelRef): Promise<ProductModelRef> {
const selection = await this.resolveModel(model);
if (selection.managedCapability) validateManagedReasoningChoice(model.reasoningChoice ?? { mode: 'default' }, selection.managedCapability);
return {
accountId: selection.accountId,
modelId: selection.modelId,
thinkingLevel: model.thinkingLevel,
...(selection.managedCapability ? { reasoningChoice: model.reasoningChoice ?? { mode: 'default' as const } } : {}),
};
}
@@ -924,6 +931,7 @@ export class PiConversationRuntime implements CodingConversationRuntime {
accountId: selection.accountId,
modelId: selection.modelId,
thinkingLevel,
...(selection.managedCapability ? { reasoningChoice: { mode: 'default' as const } } : {}),
},
modelResolution: 'resolved',
};
@@ -981,6 +989,16 @@ export class PiConversationRuntime implements CodingConversationRuntime {
true,
);
}
const selection = current.model.accountId === 'niancode-user-models' ? await this.resolveModel(current.model) : undefined;
if (selection?.managedCapability) {
const choice = input.reasoningChoice ?? (input.thinkingLevel === 'off'
? { mode: 'disabled' as const } : { mode: 'enabled' as const, effort: input.thinkingLevel });
validateManagedReasoningChoice(choice, selection.managedCapability);
await this.pool.request(input.conversationId, { type: 'set_thinking_level', level: managedPiThinkingLevel(choice) });
return this.persistEffectiveThinking(input.conversationId, {
model: { ...current.model, thinkingLevel: 'off', reasoningChoice: choice }, modelResolution: 'resolved',
}, {}, {}, true, true);
}
const capabilities = await this.pool.request<{ levels?: unknown }>(
input.conversationId,
{ type: 'get_available_thinking_levels' },
@@ -1025,6 +1043,7 @@ export class PiConversationRuntime implements CodingConversationRuntime {
async compact(conversationId: string): Promise<void> {
await this.waitForProjection(conversationId);
this.assertNoUncertainMutation(conversationId);
const managedRequest = await this.prepareManagedRequest(conversationId, false);
const runId = this.id('run');
this.acquireRunBackgroundLease(conversationId, runId);
const generation = this.pool.getState(conversationId)?.generation;
@@ -1032,7 +1051,7 @@ export class PiConversationRuntime implements CodingConversationRuntime {
let ticket;
try {
if (this.extensionHost && generation) {
await this.extensionHost.bindRun(conversationId, generation, runId);
await this.extensionHost.bindRun(conversationId, generation, runId, managedRequest);
}
ticket = this.pool.startTopLevel({
conversationId,
@@ -1317,6 +1336,7 @@ export class PiConversationRuntime implements CodingConversationRuntime {
if (snapshot.run.runId) {
this.acquireRunBackgroundLease(input.conversationId, snapshot.run.runId);
}
await this.prepareManagedRequest(input.conversationId, input.attachments.length > 0);
const images = await this.resolveImages(input.attachments);
const queuePosition = snapshot.queue.items.length + 1;
const queueId = this.id('queue');
@@ -1809,6 +1829,17 @@ export class PiConversationRuntime implements CodingConversationRuntime {
this.states.set(conversationId, createConversationReducerState(projected));
}
private async prepareManagedRequest(conversationId: string, hasImages: boolean) {
const model = this.snapshot(conversationId).conversation.model.model;
if (!model || model.accountId !== 'niancode-user-models') return undefined;
const selection = await this.resolveModel(model);
if (!selection.managedCapability) return undefined;
if (hasImages && !selection.input.includes('image')) {
throw new CodingRuntimeContractError('CODING_MODEL_UNAVAILABLE', '该模型尚未确认支持图片输入,请选择支持图片的模型', true);
}
return buildManagedModelRequest(model.modelId, model.reasoningChoice ?? { mode: 'default' }, selection.managedCapability);
}
private async persistEffectiveThinking(
conversationId: string,
requested: ConversationModelState,
@@ -1818,6 +1849,19 @@ export class PiConversationRuntime implements CodingConversationRuntime {
forcePersist = false,
): Promise<ConversationModelState> {
if (!requested.model) return clone(requested);
const selection = requested.model.accountId === 'niancode-user-models' ? await this.resolveModel(requested.model) : undefined;
if (selection?.managedCapability) {
const durable: ConversationModelState = {
model: { ...requested.model, thinkingLevel: 'off',
reasoningChoice: requested.model.reasoningChoice ?? { mode: 'default' } },
modelResolution: 'resolved',
};
const persisted = await this.registry.setModel(conversationId, durable);
this.pool.updateConversationModel(conversationId, persisted);
const result = { ...persisted, managedCapability: selection.managedCapability };
if (replaceSnapshot) this.replaceModel(conversationId, result);
return clone(result);
}
const effective = effectiveThinkingLevel(stateValue) ?? requested.model.thinkingLevel;
const available = availableThinkingLevels(capabilitiesValue);
if (!available.includes(effective)) available.push(effective);