Files
miaojingAI/scripts/test-agnes-system-model-templates.mjs
2026-06-06 20:49:18 +08:00

222 lines
10 KiB
JavaScript

import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
const repoRoot = path.resolve(import.meta.dirname, '..');
const {
AGNES_BASE_URL,
AGNES_PROVIDER_NAME,
AGNES_IMAGE_MODEL_GROUP,
AGNES_VIDEO_MODEL_GROUP,
AGNES_TEXT_MODEL_GROUP,
AGNES_IMAGE_MODEL_TEMPLATES,
AGNES_VIDEO_MODEL_TEMPLATES,
AGNES_TEXT_MODEL_TEMPLATES,
AGNES_VIDEO_FRAME_RATE,
normalizeAgnesVideoDuration,
getAgnesVideoNumFrames,
getAgnesModelCapabilities,
buildAgnesImageManifestBundle,
buildAgnesVideoManifestBundle,
buildAgnesCapabilitiesText,
} = await import('../src/lib/agnes-model-templates.ts');
async function runTest(name, fn) {
try {
await fn();
console.log(`PASS ${name}`);
} catch (error) {
console.error(`FAIL ${name}`);
console.error(error);
process.exitCode = 1;
}
}
function read(relativePath) {
return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
}
await runTest('Agnes templates cover documented image, video, and text models', () => {
assert.equal(AGNES_BASE_URL, 'https://apihub.agnes-ai.com');
assert.equal(AGNES_PROVIDER_NAME, 'Agnes AI');
assert.equal(AGNES_IMAGE_MODEL_GROUP, 'agnes-image');
assert.equal(AGNES_VIDEO_MODEL_GROUP, 'agnes-video');
assert.equal(AGNES_TEXT_MODEL_GROUP, 'agnes-text');
assert.deepEqual(AGNES_IMAGE_MODEL_TEMPLATES.map(item => item.modelName), [
'agnes-image-2.1-flash',
'agnes-image-2.0-flash',
]);
assert.deepEqual(AGNES_VIDEO_MODEL_TEMPLATES.map(item => item.modelName), ['agnes-video-v2.0']);
assert.deepEqual(AGNES_TEXT_MODEL_TEMPLATES.map(item => item.modelName), ['agnes-2.0-flash', 'agnes-1.5-flash']);
});
await runTest('Agnes image Manifest maps documented OpenAI-compatible image fields', () => {
const template = AGNES_IMAGE_MODEL_TEMPLATES.find(item => item.modelName === 'agnes-image-2.1-flash');
assert.ok(template, 'missing Agnes Image 2.1 Flash template');
const bundle = buildAgnesImageManifestBundle(template);
const provider = bundle.customProviders[0];
const profile = bundle.profiles[0];
assert.equal(profile.baseUrl, AGNES_BASE_URL);
assert.equal(profile.apiMode, 'images');
assert.equal(profile.capabilities?.supportsAspectRatio, false);
assert.deepEqual(profile.capabilities?.resolutions?.map(item => item.value), [
'1024x768',
'1024x1024',
'768x1024',
'1152x768',
'768x1152',
]);
assert.equal(provider.submit?.path, 'v1/images/generations');
assert.equal(provider.submit?.method, 'POST');
assert.equal(provider.submit?.contentType, 'json');
assert.equal(provider.submit?.body?.model, '$profile.model');
assert.equal(provider.submit?.body?.prompt, '$prompt');
assert.equal(provider.submit?.body?.size, '$params.size');
assert.equal(provider.submit?.body?.image, '$inputImages.urls');
assert.deepEqual(provider.submit?.body?.extra_body, { response_format: 'url' });
assert.equal(provider.submit?.body?.response_format, undefined);
assert.deepEqual(provider.submit?.result?.imageUrlPaths, ['data.*.url']);
assert.deepEqual(provider.submit?.result?.b64JsonPaths, ['data.*.b64_json']);
});
await runTest('Agnes video Manifest creates async task and polls by video_id', () => {
const template = AGNES_VIDEO_MODEL_TEMPLATES[0];
const bundle = buildAgnesVideoManifestBundle(template);
const provider = bundle.customProviders[0];
assert.equal(bundle.profiles[0].baseUrl, AGNES_BASE_URL);
assert.equal(bundle.profiles[0].apiMode, 'videos');
assert.equal(provider.submit?.path, 'v1/videos');
assert.equal(provider.submit?.body?.model, '$profile.model');
assert.equal(provider.submit?.body?.prompt, '$prompt');
assert.equal(provider.submit?.body?.image, '$inputImages.urls.0');
assert.equal(provider.submit?.body?.num_frames, '$params.num_frames');
assert.equal(provider.submit?.body?.negative_prompt, '$params.negative_prompt');
assert.equal(provider.submit?.body?.frame_rate, '$params.fps');
assert.equal(provider.submit?.body?.width, '$params.width');
assert.equal(provider.submit?.body?.height, '$params.height');
assert.match(provider.submit?.taskIdPath || '', /video_id/);
assert.equal(provider.poll?.path, 'agnesapi');
assert.deepEqual(provider.poll?.query, {
video_id: '{task_id}',
model_name: '$profile.model',
});
assert.equal(provider.poll?.statusPath, 'status');
assert.deepEqual(provider.poll?.successValues, ['completed']);
assert.deepEqual(provider.poll?.failureValues, ['failed']);
assert.deepEqual(provider.poll?.result?.videoUrlPaths, ['remixed_from_video_id', 'video_url', 'url']);
});
await runTest('Agnes video duration options map to documented frame counts at 24fps', () => {
assert.equal(AGNES_VIDEO_FRAME_RATE, 24);
assert.deepEqual(AGNES_VIDEO_MODEL_TEMPLATES[0].capabilities.durations?.map(item => item.value), ['3', '5', '10']);
assert.equal(normalizeAgnesVideoDuration(18), null);
assert.equal(getAgnesVideoNumFrames(3), 81);
assert.equal(getAgnesVideoNumFrames(5), 121);
assert.equal(getAgnesVideoNumFrames(10), 241);
assert.deepEqual(getAgnesModelCapabilities('agnes-video-v2.0')?.durations?.map(item => item.value), ['3', '5', '10']);
const videoRoute = read('src/app/api/generate/video/route.ts');
assert.match(videoRoute, /normalizeAgnesVideoDuration\(duration\)/);
assert.match(videoRoute, /Agnes Video V2\.0 当前仅开放 3、5、10 秒/);
assert.match(videoRoute, /const useAgnesVideoParams = isAgnesVideoApi\(resolvedCustomApiConfig\)/);
assert.match(videoRoute, /getAgnesVideoNumFrames\(resolvedAgnesDuration\)/);
assert.match(videoRoute, /fps:\s*useAgnesVideoParams\s*\?\s*AGNES_VIDEO_FRAME_RATE\s*:\s*fps/);
assert.match(videoRoute, /num_frames:\s*useAgnesVideoParams\s*\?\s*getAgnesVideoNumFrames\(resolvedAgnesDuration\)\s*:\s*undefined/);
assert.match(videoRoute, /timeoutMs:\s*useAgnesVideoParams\s*\?\s*AGNES_VIDEO_GENERATION_TIMEOUT\s*:\s*GENERATION_TIMEOUT/);
});
await runTest('Agnes video failures are reported by stage instead of raw fetch failed', () => {
const executor = read('src/lib/user-api-manifest-executor.ts');
const videoRoute = read('src/app/api/generate/video/route.ts');
const worker = read('src/lib/generation-job-worker.ts');
const runner = read('src/lib/generation-job-runner.ts');
assert.match(executor, /const stage = method === 'GET' \? '上游任务轮询' : '上游任务创建'/);
assert.match(executor, /网络连接失败,请稍后重试/);
assert.match(videoRoute, /上游已返回视频地址,但平台下载或保存结果视频失败/);
assert.match(worker, /creation history persistence failed:/);
assert.match(worker, /\(\$\{url\}\)/);
assert.match(runner, /内部生成请求网络连接失败/);
assert.match(runner, /requestInternalGenerationJson/);
});
await runTest('Agnes video polling progress is forwarded into generation job status', () => {
const executor = read('src/lib/user-api-manifest-executor.ts');
assert.match(executor, /function getManifestProgress/);
assert.match(executor, /getPathValue\(raw,\s*'progress'\)/);
assert.match(executor, /remainingSeconds/);
assert.match(executor, /上游任务创建中/);
assert.match(executor, /上游任务已创建,等待生成结果/);
assert.match(executor, /await input\.onProgress\?\.\(getManifestProgress\(raw,\s*status\)\)/);
});
await runTest('Agnes video manifest splits per-request timeout from total polling budget', () => {
const executor = read('src/lib/user-api-manifest-executor.ts');
assert.match(executor, /function getManifestRequestTimeoutMs/);
assert.match(executor, /USER_API_MANIFEST_SUBMIT_TIMEOUT_MS/);
assert.match(executor, /USER_API_MANIFEST_POLL_REQUEST_TIMEOUT_MS/);
assert.match(executor, /AGNES_VIDEO_MANIFEST_SUBMIT_TIMEOUT_MS/);
assert.match(executor, /function isAgnesVideoManifestRequest/);
assert.match(executor, /getManifestRequestTimeoutMs\(input\.timeoutMs,\s*method,\s*input\)/);
assert.match(executor, /while \(Date\.now\(\) < deadline\)/);
assert.match(executor, /isTransientPollError/);
});
await runTest('Agnes installer source creates free inactive rows with empty API key and per-row Manifest files', () => {
const installer = read('src/lib/agnes-template-installer.ts');
assert.match(installer, /encryptApiKeyForStorage\(''\)/);
assert.match(installer, /credits_per_use/);
assert.match(installer, /billingMode:\s*'free'/);
assert.match(installer, /is_active,\s*sort_order/);
assert.match(installer, /false,\s*input\.sortOffset/s);
assert.match(installer, /attachManifest\(client,\s*row,\s*bundle,\s*saveManifestFile\)/);
assert.match(installer, /syncImageModels/);
assert.match(installer, /syncVideoModels/);
assert.match(installer, /syncTextModels/);
assert.match(installer, /`\$\{AGNES_BASE_URL\}\/v1\/images\/generations`/);
assert.match(installer, /`\$\{AGNES_BASE_URL\}\/v1\/chat\/completions`/);
assert.match(installer, /const apiUrl = resolveImportedProfileApiUrl\(bundle,\s*profile\) \|\| AGNES_BASE_URL/);
assert.match(installer, /saveSystemApiManifestFile/);
assert.match(installer, /Agnes 免费模型/);
});
await runTest('Agnes system model capabilities use built-in fallback so stale manifests do not expose unstable 18s', () => {
const serverConfig = read('src/lib/server-api-config.ts');
assert.match(serverConfig, /getAgnesModelCapabilities/);
assert.match(serverConfig, /getAgnesSystemApiCapabilitiesFallback/);
assert.match(serverConfig, /getAgnesSystemApiCapabilitiesFallback\(row\)\s*\|\|\s*readManifestCapabilities/);
});
await runTest('admin UI and docs expose Agnes as system-default built-in templates, not smart import', () => {
const adminTab = read('src/components/admin/api-management-tab.tsx');
const apiReference = read('docs/codex-miaojing/api-reference.md');
const customIntegrations = read('docs/codex-miaojing/custom-integrations.md');
const featureIndex = read('docs/codex-miaojing/feature-code-index.md');
assert.match(adminTab, /agnes-capabilities/);
assert.match(adminTab, /安装 Agnes 免费模型/);
assert.match(adminTab, /免费模型/);
assert.match(apiReference, /\/api\/admin\/system-apis\/agnes-capabilities/);
assert.match(customIntegrations, /Agnes AI/);
assert.match(featureIndex, /agnes-model-templates/);
});
await runTest('Agnes capabilities text summarizes documented modules', () => {
const text = buildAgnesCapabilitiesText();
assert.match(text, /Agnes Image 2\.1 Flash/);
assert.match(text, /Agnes Image 2\.0 Flash/);
assert.match(text, /Agnes Video V2\.0/);
assert.match(text, /Agnes 2\.0 Flash/);
assert.match(text, /https:\/\/apihub\.agnes-ai\.com/);
});
if (process.exitCode) process.exit(process.exitCode);