feat: make proxyModel the primary plugin control field
- proxyModel field now determines both YAML pipeline stages AND plugin
gating behavior ('default'/'gate' = gated, 'content-pipeline' = not)
- Deprecate --gated/--no-gated CLI flags (backward compat preserved:
--no-gated maps to --proxy-model content-pipeline)
- Replace GATED column with PLUGIN in `get projects` output
- Update `describe project` to show "Plugin Config" section
- Unify proxymodel discovery: GET /proxymodels now returns both YAML
pipeline models and TypeScript plugins with type field
- `describe proxymodel gate` shows plugin hooks and extends info
- Update CLI apply schema: gated is now optional (not required)
- Regenerate shell completions
- Tests: proxymodel endpoint (5), smoke tests (8)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -127,7 +127,7 @@ const ProjectSpecSchema = z.object({
|
||||
prompt: z.string().max(10000).default(''),
|
||||
proxyMode: z.enum(['direct', 'filtered']).default('direct'),
|
||||
proxyModel: z.string().optional(),
|
||||
gated: z.boolean().default(true),
|
||||
gated: z.boolean().optional(),
|
||||
llmProvider: z.string().optional(),
|
||||
llmModel: z.string().optional(),
|
||||
servers: z.array(z.string()).default([]),
|
||||
|
||||
@@ -226,10 +226,10 @@ export function createCreateCommand(deps: CreateCommandDeps): Command {
|
||||
.argument('<name>', 'Project name')
|
||||
.option('-d, --description <text>', 'Project description', '')
|
||||
.option('--proxy-mode <mode>', 'Proxy mode (direct, filtered)')
|
||||
.option('--proxy-model <name>', 'ProxyModel pipeline name (e.g. default, subindex)')
|
||||
.option('--proxy-model <name>', 'Plugin name (default, content-pipeline, gate, none)')
|
||||
.option('--prompt <text>', 'Project-level prompt / instructions for the LLM')
|
||||
.option('--gated', 'Enable gated sessions (default: true)')
|
||||
.option('--no-gated', 'Disable gated sessions')
|
||||
.option('--gated', '[deprecated: use --proxy-model default]')
|
||||
.option('--no-gated', '[deprecated: use --proxy-model content-pipeline]')
|
||||
.option('--server <name>', 'Server name (repeat for multiple)', collect, [])
|
||||
.option('--force', 'Update if already exists')
|
||||
.action(async (name: string, opts) => {
|
||||
@@ -239,7 +239,13 @@ export function createCreateCommand(deps: CreateCommandDeps): Command {
|
||||
proxyMode: opts.proxyMode ?? 'direct',
|
||||
};
|
||||
if (opts.prompt) body.prompt = opts.prompt;
|
||||
if (opts.proxyModel) body.proxyModel = opts.proxyModel;
|
||||
if (opts.proxyModel) {
|
||||
body.proxyModel = opts.proxyModel;
|
||||
} else if (opts.gated === false) {
|
||||
// Backward compat: --no-gated → proxyModel: content-pipeline
|
||||
body.proxyModel = 'content-pipeline';
|
||||
}
|
||||
// Pass gated for backward compat with older mcpd
|
||||
if (opts.gated !== undefined) body.gated = opts.gated as boolean;
|
||||
if (opts.server.length > 0) body.servers = opts.server;
|
||||
|
||||
|
||||
@@ -142,21 +142,19 @@ function formatProjectDetail(
|
||||
lines.push(`=== Project: ${project.name} ===`);
|
||||
lines.push(`${pad('Name:')}${project.name}`);
|
||||
if (project.description) lines.push(`${pad('Description:')}${project.description}`);
|
||||
lines.push(`${pad('Gated:')}${project.gated ? 'yes' : 'no'}`);
|
||||
|
||||
// Proxy config section
|
||||
// Plugin & proxy config
|
||||
const proxyMode = project.proxyMode as string | undefined;
|
||||
const proxyModel = project.proxyModel as string | undefined;
|
||||
const proxyModel = (project.proxyModel as string | undefined) || 'default';
|
||||
const llmProvider = project.llmProvider as string | undefined;
|
||||
const llmModel = project.llmModel as string | undefined;
|
||||
if (proxyMode || proxyModel || llmProvider || llmModel) {
|
||||
lines.push('');
|
||||
lines.push('Proxy Config:');
|
||||
lines.push(` ${pad('Mode:', 18)}${proxyMode ?? 'direct'}`);
|
||||
lines.push(` ${pad('ProxyModel:', 18)}${proxyModel || 'default'}`);
|
||||
if (llmProvider) lines.push(` ${pad('LLM Provider:', 18)}${llmProvider}`);
|
||||
if (llmModel) lines.push(` ${pad('LLM Model:', 18)}${llmModel}`);
|
||||
}
|
||||
|
||||
lines.push('');
|
||||
lines.push('Plugin Config:');
|
||||
lines.push(` ${pad('Plugin:', 18)}${proxyModel}`);
|
||||
lines.push(` ${pad('Mode:', 18)}${proxyMode ?? 'direct'}`);
|
||||
if (llmProvider) lines.push(` ${pad('LLM Provider:', 18)}${llmProvider}`);
|
||||
if (llmModel) lines.push(` ${pad('LLM Model:', 18)}${llmModel}`);
|
||||
|
||||
// Servers section
|
||||
const servers = project.servers as Array<{ server: { name: string } }> | undefined;
|
||||
@@ -598,9 +596,30 @@ async function resolveLink(linkTarget: string, client: ApiClient): Promise<strin
|
||||
|
||||
function formatProxymodelDetail(model: Record<string, unknown>): string {
|
||||
const lines: string[] = [];
|
||||
const modelType = (model.type as string | undefined) ?? 'pipeline';
|
||||
lines.push(`=== ProxyModel: ${model.name} ===`);
|
||||
lines.push(`${pad('Name:')}${model.name}`);
|
||||
lines.push(`${pad('Source:')}${model.source ?? 'unknown'}`);
|
||||
lines.push(`${pad('Type:')}${modelType}`);
|
||||
|
||||
if (modelType === 'plugin') {
|
||||
if (model.description) lines.push(`${pad('Description:')}${model.description}`);
|
||||
const extendsArr = model.extends as readonly string[] | undefined;
|
||||
if (extendsArr && extendsArr.length > 0) {
|
||||
lines.push(`${pad('Extends:')}${[...extendsArr].join(', ')}`);
|
||||
}
|
||||
const hooks = model.hooks as string[] | undefined;
|
||||
if (hooks && hooks.length > 0) {
|
||||
lines.push('');
|
||||
lines.push('Hooks:');
|
||||
for (const h of hooks) {
|
||||
lines.push(` - ${h}`);
|
||||
}
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// Pipeline type
|
||||
lines.push(`${pad('Controller:')}${model.controller ?? '-'}`);
|
||||
lines.push(`${pad('Cacheable:')}${model.cacheable ? 'yes' : 'no'}`);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ interface ProjectRow {
|
||||
description: string;
|
||||
proxyMode: string;
|
||||
proxyModel: string;
|
||||
gated: boolean;
|
||||
gated?: boolean;
|
||||
ownerId: string;
|
||||
servers?: Array<{ server: { name: string } }>;
|
||||
}
|
||||
@@ -87,8 +87,7 @@ interface RbacRow {
|
||||
const projectColumns: Column<ProjectRow>[] = [
|
||||
{ header: 'NAME', key: 'name' },
|
||||
{ header: 'MODE', key: (r) => r.proxyMode ?? 'direct', width: 10 },
|
||||
{ header: 'PROXYMODEL', key: (r) => r.proxyModel || 'default', width: 12 },
|
||||
{ header: 'GATED', key: (r) => r.gated ? 'yes' : 'no', width: 6 },
|
||||
{ header: 'PLUGIN', key: (r) => r.proxyModel || 'default', width: 18 },
|
||||
{ header: 'SERVERS', key: (r) => r.servers ? String(r.servers.length) : '0', width: 8 },
|
||||
{ header: 'DESCRIPTION', key: 'description', width: 30 },
|
||||
{ header: 'ID', key: 'id' },
|
||||
@@ -196,17 +195,27 @@ const serverAttachmentColumns: Column<ServerAttachmentRow>[] = [
|
||||
interface ProxymodelRow {
|
||||
name: string;
|
||||
source: string;
|
||||
controller: string;
|
||||
stages: string[];
|
||||
cacheable: boolean;
|
||||
type?: string;
|
||||
controller?: string;
|
||||
stages?: string[];
|
||||
cacheable?: boolean;
|
||||
extends?: readonly string[];
|
||||
hooks?: string[];
|
||||
description?: string;
|
||||
}
|
||||
|
||||
const proxymodelColumns: Column<ProxymodelRow>[] = [
|
||||
{ header: 'NAME', key: 'name' },
|
||||
{ header: 'TYPE', key: (r) => r.type ?? 'pipeline', width: 10 },
|
||||
{ header: 'SOURCE', key: 'source', width: 10 },
|
||||
{ header: 'CONTROLLER', key: 'controller', width: 12 },
|
||||
{ header: 'STAGES', key: (r) => r.stages.join(', '), width: 40 },
|
||||
{ header: 'CACHEABLE', key: (r) => r.cacheable ? 'yes' : 'no', width: 10 },
|
||||
{ header: 'DETAIL', key: (r) => {
|
||||
if (r.type === 'plugin') {
|
||||
const ext = r.extends?.length ? `extends: ${[...r.extends].join(', ')}` : '';
|
||||
const hooks = r.hooks?.length ? `hooks: ${r.hooks.length}` : '';
|
||||
return [ext, hooks].filter(Boolean).join(' | ') || '-';
|
||||
}
|
||||
return r.stages?.join(', ') ?? '-';
|
||||
}, width: 45 },
|
||||
];
|
||||
|
||||
function getColumnsForResource(resource: string): Column<Record<string, unknown>>[] {
|
||||
|
||||
Reference in New Issue
Block a user