import { buildAgentCapabilities } from '@/lib/agent-api-contracts'; import { RequestValidationError } from '@/lib/image-request-utils'; import { normalizeConfiguredUpstreamHeaders, type UpstreamRequestHeaders } from '@/lib/image-upstream-profile'; import { parseImageProviderManifest } from '@/lib/image-upstream-provider-manifest'; import { NextResponse } from 'next/server'; const CONFIGURED_MARKER = 'configured'; const REDACTED_CHANNEL_FIELDS = new Set(['API_KEYS', 'MATSCA_APP_ID', 'MATSCA_APP_SECRET', 'USER_AGENT']); export async function GET() { // 保持未鉴权,便于 Agent 在发起工具调用前发现契约和鉴权要求。 return NextResponse.json(buildAgentCapabilities(readPublicCapabilitiesEnv())); } function readPublicCapabilitiesEnv(): Record { return { AGENT_STATE_BACKEND: process.env.AGENT_STATE_BACKEND, AGENT_API_TOKEN: process.env.AGENT_API_TOKEN?.trim() ? 'configured' : undefined, APP_PASSWORD: process.env.APP_PASSWORD?.trim() ? 'configured' : undefined, AGENT_DATABASE_URL: process.env.AGENT_DATABASE_URL ? 'configured' : undefined, AGENT_DB_PASSWORD: process.env.AGENT_DB_PASSWORD ? 'configured' : undefined, AGENT_DB_PASSWORD_FILE: process.env.AGENT_DB_PASSWORD_FILE ? 'configured' : undefined, AGENT_REQUEST_TTL_SECONDS: process.env.AGENT_REQUEST_TTL_SECONDS, AGENT_PUBLIC_BASE_URL: process.env.AGENT_PUBLIC_BASE_URL, ENABLE_RESPONSES_IMAGE_BACKEND: process.env.ENABLE_RESPONSES_IMAGE_BACKEND, OPENAI_RESPONSES_API_MODEL: process.env.OPENAI_RESPONSES_API_MODEL?.trim() ? 'configured' : undefined, NEXT_PUBLIC_IMAGE_STORAGE_MODE: process.env.NEXT_PUBLIC_IMAGE_STORAGE_MODE, VERCEL: process.env.VERCEL, OPENAI_API_KEY: process.env.OPENAI_API_KEY?.trim() ? 'configured' : undefined, OPENAI_API_BASE_URL: process.env.OPENAI_API_BASE_URL, OPENAI_UPSTREAM_PROFILE: process.env.OPENAI_UPSTREAM_PROFILE, OPENAI_UPSTREAM_REQUEST_MODES: process.env.OPENAI_UPSTREAM_REQUEST_MODES, OPENAI_UPSTREAM_REQUEST_MODE_PRIORITY: process.env.OPENAI_UPSTREAM_REQUEST_MODE_PRIORITY, OPENAI_UPSTREAM_PROXY_URL: process.env.OPENAI_UPSTREAM_PROXY_URL, OPENAI_UPSTREAM_USER_AGENT: readConfiguredMarker(process.env.OPENAI_UPSTREAM_USER_AGENT), UPSTREAM_USER_AGENT: readConfiguredMarker(process.env.UPSTREAM_USER_AGENT), OPENAI_ROUTING_STRATEGY: process.env.OPENAI_ROUTING_STRATEGY, OPENAI_ALLOWED_PLAIN_HTTP_API_BASE_URLS: process.env.OPENAI_ALLOWED_PLAIN_HTTP_API_BASE_URLS, IMAGE_UPSTREAM_TIMEOUT_MS: process.env.IMAGE_UPSTREAM_TIMEOUT_MS, IMAGE_STREAM_DATA_INTERVAL_TIMEOUT_MS: process.env.IMAGE_STREAM_DATA_INTERVAL_TIMEOUT_MS, IMAGE_UPSTREAM_MAX_RETRIES: process.env.IMAGE_UPSTREAM_MAX_RETRIES, ...readPublicChannelEnv(process.env) }; } function readPublicChannelEnv(env: NodeJS.ProcessEnv): Record { const publicEnv: Record = {}; for (const key of Object.keys(env)) { const match = /^OPENAI_CHANNEL_(\d+)_(ID|BASE_URL|PROXY_URL|UPSTREAM_PROFILE|PROVIDER_MANIFEST|REQUEST_MODES|REQUEST_MODE_PRIORITY|API_KEYS|MATSCA_APP_ID|MATSCA_APP_SECRET|USER_AGENT|UPSTREAM_HEADERS_JSON)$/.exec( key ); if (!match) continue; const [, , fieldName] = match; publicEnv[key] = readPublicChannelEnvValue(key, fieldName, env[key]); } return publicEnv; } function readPublicChannelEnvValue(key: string, fieldName: string, value: string | undefined): string | undefined { if (REDACTED_CHANNEL_FIELDS.has(fieldName)) return readConfiguredMarker(value); if (fieldName === 'UPSTREAM_HEADERS_JSON') return sanitizePublicUpstreamHeadersJson(key, value); if (fieldName === 'PROVIDER_MANIFEST') return sanitizePublicProviderManifest(value); return value; } function readConfiguredMarker(value: string | undefined): string | undefined { return value?.trim() ? CONFIGURED_MARKER : undefined; } function sanitizePublicUpstreamHeadersJson(key: string, value: string | undefined): string | undefined { if (!value?.trim()) return undefined; let parsed: unknown; try { parsed = JSON.parse(value); } catch { throw new RequestValidationError(`${key} 必须是 JSON 对象。`, 500); } if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { throw new RequestValidationError(`${key} 必须是 JSON 对象。`, 500); } const headers: UpstreamRequestHeaders = {}; for (const [name, headerValue] of Object.entries(parsed)) { if (typeof headerValue !== 'string') { throw new RequestValidationError(`${key} 的值必须都是字符串。`, 500); } headers[name] = headerValue; } const normalized = normalizeConfiguredUpstreamHeaders(headers, key); const sanitized = Object.fromEntries( Object.keys(normalized || {}).map((name) => [name, CONFIGURED_MARKER]) ) as UpstreamRequestHeaders; return JSON.stringify(sanitized); } function sanitizePublicProviderManifest(value: string | undefined): string | undefined { if (!value?.trim()) return undefined; return JSON.stringify(parseImageProviderManifest(value)); }