diff --git a/packages/ai/src/api/pi-messages.ts b/packages/ai/src/api/pi-messages.ts index 0af13f88..ee83f4ec 100644 --- a/packages/ai/src/api/pi-messages.ts +++ b/packages/ai/src/api/pi-messages.ts @@ -103,14 +103,11 @@ export class PiMessagesResponseError extends Error { } } -function isRecord(value: unknown): value is Record { - return typeof value === "object" && value !== null && !Array.isArray(value); -} - function parsePiMessagesErrorBody(body: string): PiMessagesErrorBody | undefined { try { - const parsed = JSON.parse(body) as unknown; - return isRecord(parsed) && isRecord(parsed.error) ? (parsed as PiMessagesErrorBody) : undefined; + const parsed = JSON.parse(body) as PiMessagesErrorBody | null; + const error = parsed?.error; + return parsed && typeof error === "object" && error !== null && !Array.isArray(error) ? parsed : undefined; } catch { return undefined; } diff --git a/packages/ai/src/providers/radius-config.ts b/packages/ai/src/providers/radius-config.ts index dc96a300..a2715aae 100644 --- a/packages/ai/src/providers/radius-config.ts +++ b/packages/ai/src/providers/radius-config.ts @@ -23,28 +23,29 @@ export type RadiusOAuthCredential = OAuthCredential & { gatewayConfig?: RadiusGatewayConfig; }; -function isRecord(value: unknown): value is Record { - return typeof value === "object" && value !== null && !Array.isArray(value); -} - function isRadiusGatewayModel(value: unknown): value is RadiusGatewayModel { + if (typeof value !== "object" || value === null || Array.isArray(value)) return false; + const model = value as Partial; return ( - isRecord(value) && - typeof value.id === "string" && - typeof value.name === "string" && - typeof value.reasoning === "boolean" && - Array.isArray(value.input) && - isRecord(value.cost) && - typeof value.contextWindow === "number" && - typeof value.maxTokens === "number" + typeof model.id === "string" && + typeof model.name === "string" && + typeof model.reasoning === "boolean" && + Array.isArray(model.input) && + typeof model.cost === "object" && + model.cost !== null && + !Array.isArray(model.cost) && + typeof model.contextWindow === "number" && + typeof model.maxTokens === "number" ); } function sanitizeRadiusGatewayConfig(config: unknown): RadiusGatewayConfig | undefined { - if (!isRecord(config) || typeof config.baseUrl !== "string" || !Array.isArray(config.models)) return undefined; + if (typeof config !== "object" || config === null || Array.isArray(config)) return undefined; + const { baseUrl, models } = config as Partial; + if (typeof baseUrl !== "string" || !Array.isArray(models)) return undefined; return { - baseUrl: config.baseUrl, - models: config.models.filter(isRadiusGatewayModel).map((model) => ({ ...model })), + baseUrl, + models: models.filter(isRadiusGatewayModel).map((model) => ({ ...model })), }; } diff --git a/scripts/publish-model-catalog.mjs b/scripts/publish-model-catalog.mjs index a8a30d43..922e19eb 100644 --- a/scripts/publish-model-catalog.mjs +++ b/scripts/publish-model-catalog.mjs @@ -65,10 +65,6 @@ function readJson(path) { return JSON.parse(readFileSync(path, "utf8")); } -function isRecord(value) { - return typeof value === "object" && value !== null && !Array.isArray(value); -} - function validateBundle(inputDir) { const modelsPath = join(inputDir, "models.json"); const providerIndexPath = join(inputDir, "providers.json"); @@ -77,7 +73,9 @@ function validateBundle(inputDir) { const models = JSON.parse(modelsBytes.toString("utf8")); const providerIds = readJson(providerIndexPath); - if (!isRecord(models)) throw new Error("models.json must contain an object"); + if (typeof models !== "object" || models === null || Array.isArray(models)) { + throw new Error("models.json must contain an object"); + } if (!Array.isArray(providerIds) || !providerIds.every((value) => typeof value === "string")) { throw new Error("providers.json must contain an array of provider IDs"); } @@ -93,13 +91,21 @@ function validateBundle(inputDir) { let modelCount = 0; for (const providerId of providerIds) { const providerModels = models[providerId]; - if (!isRecord(providerModels)) throw new Error(`Provider catalog must be an object: ${providerId}`); + if (typeof providerModels !== "object" || providerModels === null || Array.isArray(providerModels)) { + throw new Error(`Provider catalog must be an object: ${providerId}`); + } const providerFile = readJson(join(providersDir, `${providerId}.json`)); if (!isDeepStrictEqual(providerFile, providerModels)) { throw new Error(`Provider shard does not match models.json: ${providerId}`); } for (const [modelId, model] of Object.entries(providerModels)) { - if (!isRecord(model) || model.id !== modelId || model.provider !== providerId) { + if ( + typeof model !== "object" || + model === null || + Array.isArray(model) || + model.id !== modelId || + model.provider !== providerId + ) { throw new Error(`Invalid model entry: ${providerId}/${modelId}`); } modelCount++; @@ -181,13 +187,20 @@ function uploadJson(bucket, endpoint, sourcePath, key, cacheControl) { } function validateIndex(index) { - if (!isRecord(index) || index.schemaVersion !== CATALOG_SCHEMA_VERSION) { + if ( + typeof index !== "object" || + index === null || + Array.isArray(index) || + index.schemaVersion !== CATALOG_SCHEMA_VERSION + ) { throw new Error(`Existing ${CATALOG_INDEX_KEY} has an unsupported schema`); } if (!Array.isArray(index.catalogs)) throw new Error(`Existing ${CATALOG_INDEX_KEY} has no catalogs array`); for (const catalog of index.catalogs) { if ( - !isRecord(catalog) || + typeof catalog !== "object" || + catalog === null || + Array.isArray(catalog) || typeof catalog.minimumPiVersion !== "string" || typeof catalog.revision !== "string" ) {