#!/usr/bin/env node import { loadPrivateAgentEnvFile, resolvePlaygroundBaseUrl } from '../skills/gpt-image-playground-agent/scripts/lib/script-utils.mjs'; import { CHANNEL_REQUEST_MODES, CHANNEL_REQUEST_MODE_SMOKE_CASES } from '../src/lib/channel-request-mode-values.mjs'; import { isMainModule, parseJsonPayload, pickFailureOutput, printJson, redactBaseUrl, runCommand } from './command-center-utils.mjs'; import { fileURLToPath } from 'node:url'; const GENERATE_SCRIPT = fileURLToPath( new URL('../skills/gpt-image-playground-agent/scripts/generate-image.mjs', import.meta.url) ); const EDIT_SCRIPT = fileURLToPath( new URL('../skills/gpt-image-playground-agent/scripts/edit-image.mjs', import.meta.url) ); const AGENT_DOCTOR_TIMEOUT_MS = 75_000; const ORCHESTRATION_GENERATE_SMOKE_NAME = 'orchestration_generate_1k'; const AGENT_GENERATE_SMOKE_NAME = 'agent_generate_1k'; const PAGE_SSE_GENERATE_SMOKE_NAME = 'responses_page_sse_generate_1k'; const RESPONSES_AGENT_GENERATE_SMOKE_NAME = 'responses_agent_generate_1k'; export function buildAgentDoctorArgs() { return [GENERATE_SCRIPT, '--contract-check', '--timeout-ms', '60000', 'contract check']; } export function buildAgentDoctorContractArgs(baseUrl) { const args = [GENERATE_SCRIPT, '--contract-check', '--timeout-ms', '60000']; if (baseUrl) args.push('--base-url', baseUrl); args.push('contract check'); return args; } function parseArgs(argv) { const parsed = { help: false, allowBillable: false, timeoutMs: 60_000, baseUrl: undefined, editImage: undefined }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (arg === '--help' || arg === '-h') parsed.help = true; else if (arg === '--allow-billable') parsed.allowBillable = true; else if (arg === '--timeout-ms') parsed.timeoutMs = readPositiveInteger(readOptionValue(argv, (index += 1), arg), '--timeout-ms'); else if (arg === '--base-url') parsed.baseUrl = readOptionValue(argv, (index += 1), arg); else if (arg === '--edit-image') parsed.editImage = readOptionValue(argv, (index += 1), arg); else throw new Error(`未知参数:${arg}`); } return parsed; } function printHelp() { console.log(`用法: npm run agent:doctor npm run agent:doctor -- --base-url https://your-space.hf.space --allow-billable --edit-image /path/to/reference.png 环境变量: GPT_IMAGE_PLAYGROUND_URL 服务基础地址,默认 http://localhost:4783。 GPT_IMAGE_AGENT_TOKEN capabilities 需要 bearer 鉴权时使用。 GPT_IMAGE_APP_PASSWORD_HASH capabilities 需要页面密码鉴权时使用。 选项: --base-url 显式服务地址,优先于 GPT_IMAGE_PLAYGROUND_URL 和本地探测。 --timeout-ms HTTP 探测和 smoke 超时,默认 60000。 --edit-image 真实 edit smoke 使用的参考图。 --allow-billable 显式允许真实 1K/2K smoke 请求。 agent:doctor 默认只读、非计费。真实 generate/edit smoke 必须显式添加 --allow-billable。`); } async function main() { loadPrivateAgentEnvFile(); const options = parseArgs(process.argv.slice(2)); if (options.help) { printHelp(); return; } const baseUrlInfo = resolvePlaygroundBaseUrl(options.baseUrl, process.env); const baseUrl = baseUrlInfo.baseUrl; const contract = runContractCheck(baseUrl); const capabilities = await readJsonLayer('capabilities', `${baseUrl}/api/agent/capabilities`, options.timeoutMs); const runtime = await readJsonLayer('runtime', `${baseUrl}/api/runtime-capabilities`, options.timeoutMs); const smoke = options.allowBillable ? runBillableSmoke(options, baseUrl) : buildSkippedSmoke(options); const layers = buildLayers({ capabilities, runtime, contract, smoke }); printJson({ ok: layers.every((layer) => layer.ok || layer.skipped), command: 'agent:doctor', billable: options.allowBillable, base_url: redactBaseUrl(baseUrl), service_base_url: redactBaseUrl(baseUrl), service_base_url_source: baseUrlInfo.source, interactive_confirmation_required: baseUrlInfo.interactive_confirmation_required, layers, summary: buildSummary({ capabilities, runtime, contract, smoke }) }); if (layers.some((layer) => !layer.ok && !layer.skipped)) process.exit(1); } function runContractCheck(baseUrl) { const result = runCommand(process.execPath, buildAgentDoctorContractArgs(baseUrl), { env: { ...process.env, GPT_IMAGE_AGENT_CONTRACT_CHECK: '1' }, timeoutMs: AGENT_DOCTOR_TIMEOUT_MS }); if (!result.ok) { return { ok: false, output: pickFailureOutput(result), elapsed_ms: result.elapsed_ms }; } return { ok: true, elapsed_ms: result.elapsed_ms, body: result.stdout ? parseJsonPayload(result.stdout, 'agent contract check') : {} }; } async function readJsonLayer(name, url, timeoutMs) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); try { const response = await fetch(url, { headers: authHeaders(), signal: controller.signal }); const text = await response.text(); if (!response.ok) throw new Error(`${safePathname(url)} failed with HTTP ${response.status}${formatBodySnippet(text)}`); return { ok: true, body: text ? JSON.parse(text) : {} }; } catch (error) { return { ok: false, error: error instanceof Error ? error.message : String(error), name }; } finally { clearTimeout(timer); } } function buildSkippedSmoke(options) { return { ok: true, skipped: true, reason: 'requires --allow-billable', checks: [ { name: ORCHESTRATION_GENERATE_SMOKE_NAME, skipped: true, reason: 'requires --allow-billable' }, { name: AGENT_GENERATE_SMOKE_NAME, skipped: true, reason: 'requires --allow-billable' }, { name: PAGE_SSE_GENERATE_SMOKE_NAME, skipped: true, reason: 'requires --allow-billable' }, { name: RESPONSES_AGENT_GENERATE_SMOKE_NAME, skipped: true, reason: 'requires --allow-billable' }, { name: 'edit_1k', skipped: true, reason: options.editImage ? 'requires --allow-billable' : 'requires --allow-billable and --edit-image' }, { name: 'page_sse_edit_2k', skipped: true, reason: options.editImage ? 'requires --allow-billable' : 'requires --allow-billable and --edit-image' } ] }; } function runBillableSmoke(options, baseUrl) { const checks = [ runSmokeCommand(ORCHESTRATION_GENERATE_SMOKE_NAME, [ GENERATE_SCRIPT, '--base-url', baseUrl, '--allow-billable', '--timeout-ms', String(options.timeoutMs), '--size', '1024x1024', '--quality', 'low', '--image-backend', 'images-api', '--stream-mode', 'non_stream', '--idempotency-key', `agent-doctor-orchestration-generate-${Date.now()}`, 'agent doctor orchestration 1k generate smoke' ]), runSmokeCommand(AGENT_GENERATE_SMOKE_NAME, [ GENERATE_SCRIPT, '--base-url', baseUrl, '--allow-billable', '--agent', '--timeout-ms', String(options.timeoutMs), '--size', '1024x1024', '--quality', 'low', '--image-backend', 'images-api', '--stream-mode', 'non_stream', '--idempotency-key', `agent-doctor-agent-generate-${Date.now()}`, 'agent doctor agent JSON 1k generate smoke' ]), runSmokeCommand(PAGE_SSE_GENERATE_SMOKE_NAME, [ GENERATE_SCRIPT, '--base-url', baseUrl, '--allow-billable', '--page-sse', '--timeout-ms', String(options.timeoutMs), '--size', '1024x1024', '--quality', 'low', '--image-backend', 'responses-image-generation', '--stream-mode', 'stream', '--streaming-strategy', 'responses-sse', '--idempotency-key', `agent-doctor-responses-page-sse-generate-${Date.now()}`, 'agent doctor responses page SSE generate smoke' ]), runSmokeCommand(RESPONSES_AGENT_GENERATE_SMOKE_NAME, [ GENERATE_SCRIPT, '--base-url', baseUrl, '--allow-billable', '--agent', '--timeout-ms', String(options.timeoutMs), '--size', '1024x1024', '--quality', 'low', '--image-backend', 'responses-image-generation', '--stream-mode', 'non_stream', '--idempotency-key', `agent-doctor-responses-agent-generate-${Date.now()}`, 'agent doctor responses non-stream generate smoke' ]) ]; if (options.editImage) { checks.push( runSmokeCommand('edit_1k', [ EDIT_SCRIPT, '--base-url', baseUrl, '--allow-billable', '--agent', '--timeout-ms', String(options.timeoutMs), '--size', '1024x1024', '--quality', 'low', '--idempotency-key', `agent-doctor-edit-${Date.now()}`, options.editImage, 'agent doctor 1k edit smoke' ]) ); checks.push( runSmokeCommand('page_sse_edit_2k', [ EDIT_SCRIPT, '--base-url', baseUrl, '--allow-billable', '--page-sse', '--timeout-ms', String(options.timeoutMs), '--size', '2048x2048', '--quality', 'low', '--idempotency-key', `agent-doctor-page-sse-edit-${Date.now()}`, options.editImage, 'agent doctor 2k page SSE edit smoke' ]) ); } else { checks.push({ name: 'edit_1k', ok: true, skipped: true, reason: 'requires --edit-image' }); checks.push({ name: 'page_sse_edit_2k', ok: true, skipped: true, reason: 'requires --edit-image' }); } return { ok: checks.every((check) => check.ok || check.skipped), skipped: false, checks }; } function runSmokeCommand(name, args) { const result = runCommand(process.execPath, args, { env: process.env, timeoutMs: AGENT_DOCTOR_TIMEOUT_MS }); return { name, ok: result.ok, elapsed_ms: result.elapsed_ms, ...(result.ok ? {} : { output: pickFailureOutput(result) }) }; } function buildLayers({ capabilities, runtime, contract, smoke }) { return [ { name: 'capabilities', ok: capabilities.ok, endpoint: '/api/agent/capabilities', ...(capabilities.ok ? summarizeCapabilities(capabilities.body) : { error: capabilities.error }) }, { name: 'contract_check', ok: contract.ok, billable: false, ...(contract.ok ? { checks: contract.body.checks || [] } : { error: contract.output }) }, { name: 'runtime_backend', ok: runtime.ok, endpoint: '/api/runtime-capabilities', ...(runtime.ok ? summarizeRuntime(runtime.body) : { error: runtime.error }) }, { name: 'state_backend', ok: capabilities.ok, ...(capabilities.ok ? summarizeStateBackend(capabilities.body) : { error: capabilities.error }) }, { name: 'responses_gpt2image_readiness', ok: capabilities.ok && runtime.ok, ...(capabilities.ok && runtime.ok ? summarizeResponsesReadiness(capabilities.body, runtime.body) : { error: 'requires capabilities and runtime layers' }) }, { name: 'billable_smoke', ok: smoke.ok, skipped: smoke.skipped, checks: smoke.checks, ...(smoke.reason ? { reason: smoke.reason } : {}) } ]; } function summarizeCapabilities(body) { return { page_sse: body?.agent_streaming?.page_sse?.supported === true, page_sse_declared_supported: body?.agent_streaming?.page_sse?.supported === true, page_sse_auth_required: body?.agent_streaming?.page_sse?.auth?.required === true, page_sse_auth_ready: body?.agent_streaming?.page_sse?.auth?.required === true ? Boolean(process.env.GPT_IMAGE_APP_PASSWORD_HASH) : true, page_sse_auth_next_action: body?.agent_streaming?.page_sse?.auth?.required === true && !process.env.GPT_IMAGE_APP_PASSWORD_HASH ? 'Set GPT_IMAGE_APP_PASSWORD_HASH in private local env before using --page-sse.' : undefined, agent_jobs: body?.agent_jobs?.supported === true, routing_rules: Boolean(body?.routing_rules), executable_routing_rules: Boolean(body?.routing_rules?.high_resolution_edit?.conditions), request_modes_supported: readRequestModeList(body?.supported?.request_modes), request_modes_by_channel: readCapabilitiesRequestModesByChannel(body) }; } function summarizeRuntime(body) { return { default_stream_mode: body?.streaming?.defaultMode, streaming_unavailable_scope: body?.streaming?.unavailableMarkScope, responses_image_backend: body?.responsesImageBackend?.enabled === true, streaming_batch_enabled: body?.streamingBatch?.enabled === true, configured_request_modes: readRequestModeList(body?.channelRouting?.configuredRequestModes), effective_request_modes: readRequestModeList(body?.channelRouting?.effectiveRequestModes), effective_request_modes_by_channel: readRuntimeRequestModesByChannel(body) }; } function summarizeStateBackend(body) { return { backend: body?.defaults?.state_backend, image_storage_mode: body?.storage?.image_storage_mode, postgres_configured: body?.storage?.postgres_configured === true }; } function summarizeResponsesReadiness(capabilities, runtime) { const requirements = capabilities?.supported?.image_backend_requirements?.['responses-image-generation']; return { declared_supported: requirements?.supported === true, backend_supported: requirements?.supported === true, backend_enabled: requirements?.enabled === true, runtime_enabled: runtime?.responsesImageBackend?.enabled === true, missing_env: requirements?.missing_env || [], gpt2image_real_smoke_case: 'gpt2image-responses-sse', real_smoke_gate: 'npm run smoke:image-upstream-real -- --env-file-if-exists .env.real-smoke.local --case gpt2image-responses-sse --allow-billable', real_smoke_gates: [ 'npm run smoke:image-upstream-real -- --env-file-if-exists .env.real-smoke.local --case sub2api-responses-json --allow-billable', 'npm run smoke:image-upstream-real -- --env-file-if-exists .env.real-smoke.local --case gpt2image-responses-sse --allow-billable' ] }; } function buildSummary({ capabilities, runtime, contract, smoke }) { return { capabilities: capabilities.ok ? 'ok' : 'failed', contract_check: contract.ok ? 'ok' : 'failed', runtime: runtime.ok ? 'ok' : 'failed', state_backend: capabilities.ok ? capabilities.body?.defaults?.state_backend : 'unknown', page_sse_declared_supported: capabilities.ok ? capabilities.body?.agent_streaming?.page_sse?.supported === true : false, page_sse_auth_ready: capabilities.ok && capabilities.body?.agent_streaming?.page_sse?.auth?.required === true ? Boolean(process.env.GPT_IMAGE_APP_PASSWORD_HASH) : capabilities.ok, page_sse_real_smoke: summarizePageSseSmoke(smoke), orchestration_generate_smoke: summarizeSmokeCheck(smoke, ORCHESTRATION_GENERATE_SMOKE_NAME), agent_generate_smoke: summarizeSmokeCheck(smoke, AGENT_GENERATE_SMOKE_NAME), responses_page_sse_generate_smoke: summarizeSmokeCheck(smoke, PAGE_SSE_GENERATE_SMOKE_NAME), responses_agent_generate_smoke: summarizeSmokeCheck(smoke, RESPONSES_AGENT_GENERATE_SMOKE_NAME), real_smoke_checks: summarizeSmokeChecks(smoke), request_modes: buildRequestModeSummary({ capabilities: capabilities.ok ? capabilities.body : undefined, runtime: runtime.ok ? runtime.body : undefined, smoke }), runtime_environment: buildRuntimeEnvironmentSummary({ capabilities: capabilities.ok ? capabilities.body : undefined, runtime: runtime.ok ? runtime.body : undefined }), responses_gpt2image_ready: capabilities.ok && runtime.ok ? capabilities.body?.supported?.image_backend_requirements?.['responses-image-generation']?.enabled === true && runtime.body?.responsesImageBackend?.enabled === true : false, responses_image_backend_declared_supported: capabilities.ok ? capabilities.body?.supported?.image_backend_requirements?.['responses-image-generation']?.supported === true : false, billable_smoke: smoke.skipped ? 'skipped' : smoke.ok ? 'ok' : 'failed' }; } function buildRuntimeEnvironmentSummary({ capabilities, runtime }) { return { state_backend: capabilities?.defaults?.state_backend || 'unknown', image_storage_mode: capabilities?.storage?.image_storage_mode || 'unknown', postgres_configured: capabilities?.storage?.postgres_configured === true, page_sse_auth_required: capabilities?.agent_streaming?.page_sse?.auth?.required === true, agent_auth_schemes: Array.isArray(capabilities?.auth?.schemes) ? capabilities.auth.schemes : [], orchestration_endpoint: capabilities?.orchestration?.endpoint || null, orchestration_transport_selection: capabilities?.orchestration?.transport_selection || null, request_mode_control_policy: capabilities?.request_mode_controls?.agent_client_policy || null, request_mode_controls: runtime?.channelRouting?.requestModeControls || capabilities?.request_mode_controls || null, runtime_strategy: runtime?.channelRouting?.strategy || null, effective_request_modes: readRequestModeList(runtime?.channelRouting?.effectiveRequestModes), streaming_batch_enabled: runtime?.streamingBatch?.enabled === true, recommended_concurrency: runtime?.streamingBatch?.recommendedConcurrency ?? null, channel_queue_enabled: runtime?.channelQueue?.enabled === true, channel_queue_capacity_per_credential: runtime?.channelQueue?.capacityPerCredential ?? null }; } function summarizeSmokeCheck(smoke, name) { const check = smoke.checks?.find((item) => item.name === name); if (!check || check.skipped) return 'skipped'; return check.ok ? 'passed' : 'failed'; } function summarizePageSseSmoke(smoke) { const states = [PAGE_SSE_GENERATE_SMOKE_NAME, 'page_sse_edit_2k'].map((name) => summarizeSmokeCheck(smoke, name)); if (states.includes('failed')) return 'failed'; if (states.includes('passed')) return 'passed'; return 'skipped'; } function summarizeSmokeChecks(smoke) { return { orchestration_generate_1k: summarizeSmokeCheck(smoke, ORCHESTRATION_GENERATE_SMOKE_NAME), agent_generate_1k: summarizeSmokeCheck(smoke, AGENT_GENERATE_SMOKE_NAME), responses_page_sse_generate_1k: summarizeSmokeCheck(smoke, PAGE_SSE_GENERATE_SMOKE_NAME), responses_agent_generate_1k: summarizeSmokeCheck(smoke, RESPONSES_AGENT_GENERATE_SMOKE_NAME), agent_edit_1k: summarizeSmokeCheck(smoke, 'edit_1k'), page_sse_edit_2k: summarizeSmokeCheck(smoke, 'page_sse_edit_2k') }; } function buildRequestModeSummary({ capabilities, runtime, smoke }) { const supported = readRequestModeList(capabilities?.supported?.request_modes); const configured = readRequestModeList(runtime?.channelRouting?.configuredRequestModes); const effective = readRequestModeList(runtime?.channelRouting?.effectiveRequestModes); const adminWhitelistByChannel = readCapabilitiesRequestModesByChannel(capabilities); const effectiveByChannel = readRuntimeRequestModesByChannel(runtime); const smokeSummary = Object.fromEntries( CHANNEL_REQUEST_MODES.map((mode) => [mode, summarizeRequestModeSmoke(smoke, mode)]) ); const gaps = buildRequestModeGaps({ supported, configured, effective, adminWhitelistByChannel, effectiveByChannel, smoke: smokeSummary }); return { supported, configured, effective, admin_whitelist_by_channel: adminWhitelistByChannel, effective_by_channel: effectiveByChannel, smoke: smokeSummary, gaps, suggested_channel_env_key: 'OPENAI_CHANNEL_N_REQUEST_MODES', suggested_effective_value: buildSuggestedEffectiveRequestModes({ effective, smoke: smokeSummary }).join(','), next_action: buildRequestModeNextAction({ effective, gaps, smoke: smokeSummary }) }; } function buildSuggestedEffectiveRequestModes({ effective, smoke }) { if (Object.values(smoke).every((value) => value.state === 'skipped')) return effective; return effective.filter((mode) => smoke[mode]?.state === 'passed'); } function buildRequestModeGaps({ supported, configured, effective, adminWhitelistByChannel, effectiveByChannel, smoke }) { const supportedModes = Array.isArray(supported) ? supported : []; const configuredModes = Array.isArray(configured) ? configured : []; const effectiveModes = Array.isArray(effective) ? effective : []; const adminChannels = normalizeRequestModeChannelEntries(adminWhitelistByChannel); const effectiveChannels = normalizeRequestModeChannelEntries(effectiveByChannel); const gaps = []; if (effectiveModes.length === 0) { gaps.push({ code: 'no_effective_request_modes', severity: 'critical', message: '当前服务没有可用上游请求方式;检查 OPENAI_CHANNEL_N_REQUEST_MODES、渠道健康和 API key。' }); } const unrecognizedModes = collectUnrecognizedRequestModes( supportedModes, configuredModes, effectiveModes, adminChannels.flatMap((channel) => channel.request_modes), effectiveChannels.flatMap((channel) => channel.request_modes) ); if (unrecognizedModes.length > 0) { gaps.push({ code: 'unrecognized_request_modes', severity: 'warning', request_modes: unrecognizedModes, message: '服务返回了当前 Agent 未识别的 request mode;升级 skill 或确认服务端模式名称。' }); } const unsupportedConfigured = configuredModes.filter((mode) => !supportedModes.includes(mode)); if (unsupportedConfigured.length > 0) { gaps.push({ code: 'configured_unsupported_request_modes', severity: 'warning', request_modes: unsupportedConfigured, message: '配置中包含服务不支持的 request mode。' }); } const configuredButIneffective = configuredModes.filter( (mode) => supportedModes.includes(mode) && !effectiveModes.includes(mode) ); if (configuredButIneffective.length > 0) { gaps.push({ code: 'configured_request_modes_not_effective', severity: 'warning', request_modes: configuredButIneffective, message: '部分已配置 request mode 没有在 runtime 生效;检查对应渠道 key、健康状态和白名单。' }); } const effectiveByChannelMap = new Map( effectiveChannels.map((channel) => [channel.channel_id, channel.request_modes]) ); const channelsWithoutModes = [ ...adminChannels .filter((channel) => channel.request_modes.length > 0) .filter((channel) => (effectiveByChannelMap.get(channel.channel_id) || []).length === 0), ...effectiveChannels.filter((channel) => channel.request_modes.length === 0) ]; const uniqueChannelsWithoutModes = [ ...new Map(channelsWithoutModes.map((channel) => [channel.channel_id, channel])).values() ]; if (uniqueChannelsWithoutModes.length > 0) { gaps.push({ code: 'channels_without_effective_request_modes', severity: 'warning', channel_ids: uniqueChannelsWithoutModes.map((channel) => channel.channel_id), message: '部分渠道没有生效 request mode。' }); } const failedSmokeModes = Object.entries(smoke) .filter(([, value]) => value.state === 'failed') .map(([mode]) => mode); if (failedSmokeModes.length > 0) { gaps.push({ code: 'request_mode_smoke_failed', severity: 'critical', request_modes: failedSmokeModes, message: '真实 smoke 显示部分 request mode 不可用;不要写入渠道白名单。' }); } return gaps; } function normalizeRequestModeChannelEntries(value) { if (!Array.isArray(value)) return []; return value .map((channel) => ({ channel_id: String(channel?.channel_id || ''), request_modes: readRequestModeList(channel?.request_modes) })) .filter((channel) => channel.channel_id); } function collectUnrecognizedRequestModes(...modeLists) { const unrecognized = []; const seen = new Set(); for (const modes of modeLists) { if (!Array.isArray(modes)) continue; for (const mode of modes) { if (CHANNEL_REQUEST_MODES.includes(mode) || seen.has(mode)) continue; seen.add(mode); unrecognized.push(mode); } } return unrecognized; } function buildRequestModeNextAction({ effective, gaps, smoke }) { if (effective.length === 0) { return '先用 probe-upstream-image.mjs 或 npm run smoke:image-upstream-real -- --allow-billable 探测上游,再配置 OPENAI_CHANNEL_N_REQUEST_MODES。'; } if (gaps.some((gap) => gap.code === 'request_mode_smoke_failed')) { return '从 OPENAI_CHANNEL_N_REQUEST_MODES 移除 smoke 失败的 request mode,只保留通过的模式。'; } if ( gaps.some((gap) => ['configured_request_modes_not_effective', 'channels_without_effective_request_modes'].includes(gap.code) ) ) { return '先修正未生效的渠道 request mode、API key 或健康状态,再用 --allow-billable smoke 验证真实可用性。'; } if (Object.values(smoke).every((value) => value.state === 'skipped')) { return '当前只验证了配置可见性;真实渠道可用性需要显式 --allow-billable smoke。'; } return '当前 request mode 配置可见;以 effective 和 smoke passed 的交集作为管理员白名单候选。'; } function summarizeRequestModeSmoke(smoke, mode) { const checks = CHANNEL_REQUEST_MODE_SMOKE_CASES[mode] || []; if (checks.length === 0) { return { state: 'skipped', checks: [], billable: false }; } const states = checks.map((name) => summarizeSmokeCheck(smoke, name)); return { state: states.includes('failed') ? 'failed' : states.includes('passed') ? 'passed' : 'skipped', checks, billable: smoke.skipped !== true }; } function readCapabilitiesRequestModesByChannel(body) { const channels = Array.isArray(body?.upstream_request_headers?.channels) ? body.upstream_request_headers.channels : []; return channels .map((channel) => ({ channel_id: String(channel?.id || ''), request_modes: readRequestModeList(channel?.request_modes) })) .filter((channel) => channel.channel_id); } function readRuntimeRequestModesByChannel(body) { const channels = Array.isArray(body?.channelRouting?.effectiveRequestModesByChannel) ? body.channelRouting.effectiveRequestModesByChannel : []; return channels .map((channel) => ({ channel_id: String(channel?.channelId || ''), request_modes: readRequestModeList(channel?.requestModes) })) .filter((channel) => channel.channel_id); } function readRequestModeList(value) { if (!Array.isArray(value)) return []; const modes = []; const seen = new Set(); for (const item of value) { const mode = typeof item === 'string' ? item.trim() : ''; if (!mode || seen.has(mode)) continue; seen.add(mode); modes.push(mode); } return modes; } function authHeaders() { if (process.env.GPT_IMAGE_AGENT_TOKEN) return { Authorization: `Bearer ${process.env.GPT_IMAGE_AGENT_TOKEN}` }; if (process.env.GPT_IMAGE_APP_PASSWORD_HASH) return { 'X-App-Password-Hash': process.env.GPT_IMAGE_APP_PASSWORD_HASH }; return {}; } function readOptionValue(argv, index, name) { const value = argv[index]; if (!value || value.startsWith('--')) throw new Error(`${name} 需要参数值。`); return value; } function readPositiveInteger(value, name) { if (!/^\d+$/.test(String(value))) throw new Error(`${name} 必须是正整数。`); const parsed = Number(value); if (!Number.isSafeInteger(parsed) || parsed < 1) throw new Error(`${name} 必须是正整数。`); return parsed; } function normalizeBaseUrl(value) { const normalized = String(value || '') .trim() .replace(/\/+$/, ''); const parsed = new URL(normalized); if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { throw new Error('base URL must use http or https.'); } if (parsed.username || parsed.password || parsed.search || parsed.hash) { throw new Error('base URL must not include credentials, query parameters, or fragments.'); } return normalized; } function safePathname(url) { try { return new URL(url).pathname; } catch { return String(url); } } function formatBodySnippet(text) { return text ? `: ${text.slice(0, 100)}` : ''; } try { if (isMainModule(import.meta.url, process.argv[1])) await main(); } catch (error) { printJson({ ok: false, error: error instanceof Error ? error.message : String(error) }); process.exit(1); }