fix: 接入 dsflash-go model catalog
This commit is contained in:
@@ -12,13 +12,14 @@ export interface CodexSecretRenderOptions {
|
||||
codexHome?: string;
|
||||
authFile?: string;
|
||||
configFile?: string;
|
||||
modelCatalogFile?: string;
|
||||
namespace?: string;
|
||||
secretName?: string;
|
||||
dryRun?: boolean;
|
||||
}
|
||||
|
||||
interface SecretFileSummary extends JsonRecord {
|
||||
key: "auth.json" | "config.toml";
|
||||
key: string;
|
||||
source: string;
|
||||
bytes: number;
|
||||
sha256: string;
|
||||
@@ -26,13 +27,12 @@ interface SecretFileSummary extends JsonRecord {
|
||||
}
|
||||
|
||||
interface SecretSourceFile {
|
||||
key: "auth.json" | "config.toml";
|
||||
key: string;
|
||||
path: string;
|
||||
validate: (content: string, file: string) => unknown;
|
||||
}
|
||||
|
||||
const defaultNamespace = "agentrun-v01";
|
||||
const secretKeys = ["auth.json", "config.toml"] as const;
|
||||
const credentialKeyPattern = /(?:api[_-]?key|token|password|secret|credential|authorization|auth)/iu;
|
||||
|
||||
export async function renderCodexProviderSecretPlan(options: CodexSecretRenderOptions = {}): Promise<JsonRecord> {
|
||||
@@ -46,10 +46,8 @@ export async function renderCodexProviderSecretPlan(options: CodexSecretRenderOp
|
||||
const namespace = nonEmpty(options.namespace, defaultNamespace);
|
||||
const secretName = nonEmpty(options.secretName, spec?.defaultSecretName ?? "agentrun-v01-provider-codex");
|
||||
const codexHome = resolvePath(nonEmpty(options.codexHome, path.join(os.homedir(), ".codex")));
|
||||
const sources: SecretSourceFile[] = [
|
||||
{ key: "auth.json", path: resolvePath(options.authFile ?? path.join(codexHome, "auth.json")), validate: validateAuthJson },
|
||||
{ key: "config.toml", path: resolvePath(options.configFile ?? path.join(codexHome, "config.toml")), validate: validateConfigToml },
|
||||
];
|
||||
const secretKeys = [...(spec?.requiredSecretKeys ?? ["auth.json", "config.toml"])] as string[];
|
||||
const sources = secretKeys.map((key): SecretSourceFile => sourceForSecretKey(key, codexHome, options));
|
||||
|
||||
const files: SecretFileSummary[] = [];
|
||||
const hash = createHash("sha256");
|
||||
@@ -89,7 +87,7 @@ export async function renderCodexProviderSecretPlan(options: CodexSecretRenderOp
|
||||
manifestSummary,
|
||||
apply: {
|
||||
attempted: false,
|
||||
command: `kubectl create secret generic ${secretName} -n ${namespace} --from-file=auth.json=<redacted> --from-file=config.toml=<redacted> --dry-run=client -o yaml | kubectl apply -f -`,
|
||||
command: `kubectl create secret generic ${secretName} -n ${namespace} ${secretKeys.map((key) => `--from-file=${key}=<redacted>`).join(" ")} --dry-run=client -o yaml | kubectl apply -f -`,
|
||||
note: "本命令只展示形状;v0.1 工具不会执行 kubectl apply,也不会输出 Secret data。",
|
||||
},
|
||||
redaction: {
|
||||
@@ -101,6 +99,13 @@ export async function renderCodexProviderSecretPlan(options: CodexSecretRenderOp
|
||||
};
|
||||
}
|
||||
|
||||
function sourceForSecretKey(key: string, codexHome: string, options: CodexSecretRenderOptions): SecretSourceFile {
|
||||
if (key === "auth.json") return { key, path: resolvePath(options.authFile ?? path.join(codexHome, key)), validate: validateAuthJson };
|
||||
if (key === "config.toml") return { key, path: resolvePath(options.configFile ?? path.join(codexHome, key)), validate: validateConfigToml };
|
||||
if (key === "model-catalog.json") return { key, path: resolvePath(options.modelCatalogFile ?? path.join(codexHome, key)), validate: validateModelCatalogJson };
|
||||
return { key, path: resolvePath(path.join(codexHome, key)), validate: validateNonEmptyFile(key) };
|
||||
}
|
||||
|
||||
async function readSecretInput(source: SecretSourceFile): Promise<string> {
|
||||
try {
|
||||
await access(source.path, fsConstants.R_OK);
|
||||
@@ -154,6 +159,36 @@ function validateConfigToml(content: string, file: string): unknown {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function validateModelCatalogJson(content: string, file: string): unknown {
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(content);
|
||||
} catch {
|
||||
throw new AgentRunError("schema-invalid", "model-catalog.json is not valid JSON", { httpStatus: 2, details: { key: "model-catalog.json", path: file } });
|
||||
}
|
||||
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
||||
throw new AgentRunError("schema-invalid", "model-catalog.json must contain a JSON object", { httpStatus: 2, details: { key: "model-catalog.json", path: file } });
|
||||
}
|
||||
const models = (parsed as { models?: unknown }).models;
|
||||
if (!Array.isArray(models) || models.length === 0) {
|
||||
throw new AgentRunError("schema-invalid", "model-catalog.json must contain a non-empty models array", { httpStatus: 2, details: { key: "model-catalog.json", path: file } });
|
||||
}
|
||||
for (const [index, model] of models.entries()) {
|
||||
const item = typeof model === "object" && model !== null && !Array.isArray(model) ? model as JsonRecord : null;
|
||||
if (!item) throw new AgentRunError("schema-invalid", `model-catalog.json models[${index}] must be an object`, { httpStatus: 2, details: { key: "model-catalog.json", path: file } });
|
||||
if (typeof item.slug !== "string" || item.slug.trim().length === 0) throw new AgentRunError("schema-invalid", `model-catalog.json models[${index}].slug is required`, { httpStatus: 2, details: { key: "model-catalog.json", path: file } });
|
||||
if (typeof item.context_window !== "number" || !Number.isFinite(item.context_window) || item.context_window <= 0) throw new AgentRunError("schema-invalid", `model-catalog.json models[${index}].context_window must be a positive number`, { httpStatus: 2, details: { key: "model-catalog.json", path: file } });
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function validateNonEmptyFile(key: string): (content: string, file: string) => unknown {
|
||||
return (content: string, file: string) => {
|
||||
if (content.length === 0) throw new AgentRunError("secret-unavailable", `${key} is empty`, { httpStatus: 2, details: { key, path: file } });
|
||||
return content;
|
||||
};
|
||||
}
|
||||
|
||||
function hasNonEmptyCredentialField(value: unknown): boolean {
|
||||
if (Array.isArray(value)) return value.some((item) => hasNonEmptyCredentialField(item));
|
||||
if (typeof value !== "object" || value === null) return false;
|
||||
|
||||
Reference in New Issue
Block a user