feat: govern chat model selection by policy

This commit is contained in:
thanhnv
2026-07-10 17:09:28 +09:00
parent f8215cd2eb
commit b0ced79af5
9 changed files with 186 additions and 12 deletions
@@ -50,4 +50,12 @@ export class ChatController {
agents(@Headers() headers: Record<string, string | string[] | undefined>) {
return ok(this.svc.listAgents(actorFromHeaders(headers)));
}
@Get('models')
models(
@Headers() headers: Record<string, string | string[] | undefined>,
@Query('modelRole') modelRole?: string,
) {
return ok(this.svc.listModels(actorFromHeaders(headers), modelRole || 'read_only'));
}
}
@@ -10,6 +10,7 @@ export interface ChatAskInput {
chatId?: string;
agentId?: string;
skillId?: string;
modelProvider?: string;
delegationLevel?: number;
}
@@ -78,6 +79,7 @@ export class ChatService {
];
if (input.agentId) args.push('--agent', input.agentId);
if (input.skillId) args.push('--skill', input.skillId);
if (input.modelProvider) args.push('--model-provider', input.modelProvider);
if (input.delegationLevel !== undefined) args.push('--delegation-level', String(input.delegationLevel));
const res = runPython(CHAT_CLI, args);
const parsed = parseJson<Record<string, any>>(res.stdout);
@@ -136,6 +138,7 @@ export class ChatService {
];
if (input.agentId) args.push('--agent', input.agentId);
if (input.skillId) args.push('--skill', input.skillId);
if (input.modelProvider) args.push('--model-provider', input.modelProvider);
if (input.delegationLevel !== undefined) args.push('--delegation-level', String(input.delegationLevel));
res.setHeader('Content-Type', 'application/x-ndjson; charset=utf-8');
res.setHeader('Cache-Control', 'no-cache');
@@ -176,6 +179,16 @@ export class ChatService {
throw new InternalServerErrorException(res.stderr || res.stdout || 'CHAT_AGENTS_FAILED');
}
listModels(actor: SettingsActor, modelRole = 'read_only') {
this.requireRead(actor);
const res = runPython(join(HARNESS_BIN, 'chat-model-resolver.py'), [
'list', '--actor', actor.actor, '--role', actor.role, '--model-role', modelRole,
]);
const parsed = parseJson<Record<string, unknown>>(res.stdout);
if (parsed) return parsed;
throw new InternalServerErrorException(res.stderr || res.stdout || 'CHAT_MODELS_FAILED');
}
private requireRead(actor: SettingsActor) {
const res = runPython(RBAC_CLI, [
'check',