Spaces:
Running
Running
File size: 5,352 Bytes
c7d34c1 11486ce b1cfe1b c7d34c1 11486ce c7d34c1 3e8ea5d c7d34c1 11486ce 96bdf6c a2c232b 11486ce c7d34c1 11486ce a2c232b b1cfe1b 11486ce | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | 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<string, string | undefined> {
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<string, string | undefined> {
const publicEnv: Record<string, string | undefined> = {};
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));
}
|