feat: support custom pi model configs, with registry-resolvable model IDs and distinct model error codes (#450)

This commit is contained in:
ezl-keygraph
2026-09-08 14:54:37 +05:30
committed by GitHub
parent 4b8131fdd5
commit d41d52f17d
20 changed files with 441 additions and 124 deletions
+8
View File
@@ -22,6 +22,7 @@ import {
FINAL_REPORT_PDF_FILENAME,
INTERNAL_DIR,
resolveConfig,
resolveModelsConfig,
resolveRepo,
resolveRunFile,
} from '../paths.js';
@@ -37,6 +38,7 @@ export interface StartArgs {
url: string;
repo: string;
config?: string;
modelsConfig?: string;
workspace?: string;
output?: string;
pipelineTesting: boolean;
@@ -231,6 +233,7 @@ export async function start(args: StartArgs): Promise<void> {
}
const repo = resolveRepo(args.repo);
const config = args.config ? resolveConfig(args.config) : undefined;
const modelsConfig = args.modelsConfig ? resolveModelsConfig(args.modelsConfig) : undefined;
const workspacesDir = getWorkspacesDir();
const workspace =
args.workspace ?? `${new URL(args.url).hostname.replace(/[^a-zA-Z0-9-]/g, '-')}_shannon-${Date.now()}`;
@@ -322,6 +325,7 @@ export async function start(args: StartArgs): Promise<void> {
containerName,
envFlags: buildEnvFlags(),
...(config && { config }),
...(modelsConfig && { modelsConfig }),
...(promptsDir && { promptsDir }),
...(outputDir && { outputDir }),
workspace,
@@ -530,6 +534,10 @@ function printInfo(args: StartArgs, workspace: string, repoPath: string, workspa
if (args.config) {
console.log(` Config: ${interactive ? path.resolve(args.config) : path.basename(args.config)}`);
}
if (args.modelsConfig) {
const shown = interactive ? path.resolve(args.modelsConfig) : path.basename(args.modelsConfig);
console.log(` Models: ${shown}`);
}
if (args.pipelineTesting) {
console.log(' Mode: Pipeline Testing');
}
+7
View File
@@ -407,6 +407,7 @@ export interface WorkerOptions {
containerName: string;
envFlags: string[];
config?: { hostPath: string; containerPath: string };
modelsConfig?: { hostPath: string; containerPath: string };
promptsDir?: string;
outputDir?: string;
workspace: string;
@@ -469,6 +470,12 @@ export function spawnWorker(opts: WorkerOptions): ChildProcess {
args.push('-v', `${opts.config.hostPath}:${opts.config.containerPath}:ro`);
}
// pi model config. The mount is the only signal the worker gets: it detects the file at
// this fixed path, so nothing about --models-config travels through the environment.
if (opts.modelsConfig) {
args.push('-v', `${opts.modelsConfig.hostPath}:${opts.modelsConfig.containerPath}:ro`);
}
// Customer-copy destination. The workflow surfaces only final report artifacts here.
if (opts.outputDir) {
args.push('-v', `${opts.outputDir}:/app/output`);
+1
View File
@@ -29,6 +29,7 @@ export const START_OPTIONS: readonly (readonly [string, string])[] = [
['-u, --url <url>', 'Target URL (required)'],
['-r, --repo <path>', 'Repository path (required)'],
['-c, --config <path>', 'Configuration file (YAML)'],
['--models-config <path>', "pi model config (models.json) defining models pi's catalogue lacks"],
['-o, --output <path>', 'Copy deliverables to this directory after the run'],
['-w, --workspace <name>', 'Named workspace (auto-resumes if it exists)'],
['-f, --follow', 'Stream the scan log until it finishes'],
+3
View File
@@ -171,6 +171,7 @@ interface ParsedStartArgs {
url: string;
repo: string;
config?: string;
modelsConfig?: string;
workspace?: string;
output?: string;
pipelineTesting: boolean;
@@ -184,6 +185,7 @@ function parseStartArgs(argv: string[]): ParsedStartArgs {
url: ['-u', '--url'],
repo: ['-r', '--repo'],
config: ['-c', '--config'],
modelsConfig: ['--models-config'],
output: ['-o', '--output'],
workspace: ['-w', '--workspace'],
},
@@ -213,6 +215,7 @@ function parseStartArgs(argv: string[]): ParsedStartArgs {
keepContainer: !!flags.keepContainer,
follow: !!flags.follow,
...(values.config && { config: values.config }),
...(values.modelsConfig && { modelsConfig: values.modelsConfig }),
...(values.workspace && { workspace: values.workspace }),
...(values.output && { output: values.output }),
};
+31 -2
View File
@@ -1,7 +1,7 @@
/**
* Path resolution for --repo and --config arguments.
* Path resolution for --repo, --config and --models-config arguments.
*
* Both --repo and --config are filesystem paths, absolute or relative to CWD.
* All three are filesystem paths, absolute or relative to CWD.
*/
import fs from 'node:fs';
@@ -108,3 +108,32 @@ export function resolveConfig(configArg: string): MountPair {
containerPath: `/app/configs/${basename}`,
};
}
/**
* Container path for a mounted pi model config. Fixed, not derived from the host filename:
* the worker detects the file here to decide whether models.json is enabled at all. Must
* match MODELS_CONFIG_PATH in the worker package.
*/
export const MODELS_CONFIG_CONTAINER_PATH = '/app/models.json';
/**
* Resolve --models-config to an absolute path and container mount. Content is left
* unparsed: pi's models.json permits comments, so JSON.parse would reject valid input,
* and pi's own loader reports schema faults far better — the worker surfaces those.
*/
export function resolveModelsConfig(modelsConfigArg: string): MountPair {
const hostPath = path.resolve(expandHome(modelsConfigArg));
if (!fs.existsSync(hostPath)) {
fail(`Model config file not found: ${hostPath}`);
}
if (!fs.statSync(hostPath).isFile()) {
fail(`Not a file: ${hostPath}`);
}
return {
hostPath,
containerPath: MODELS_CONFIG_CONTAINER_PATH,
};
}