mirror of
https://github.com/KeygraphHQ/shannon.git
synced 2026-09-16 23:15:32 +02:00
feat: support custom pi model configs, with registry-resolvable model IDs and distinct model error codes (#450)
This commit is contained in:
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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`);
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user