async function adminFetch(path: string, init?: RequestInit): Promise { const response = await fetch(path, { ...init, credentials: 'include', headers: { Accept: 'application/json', ...(init?.body ? { 'Content-Type': 'application/json' } : {}), ...(init?.headers ?? {}), }, }); const payload = (await response.json().catch(() => ({}))) as { data?: T; message?: string; error?: { code: string; message: string }; }; if (!response.ok) { throw new Error(payload?.error?.message ?? payload?.message ?? `请求失败 (${response.status})`); } return (payload.data !== undefined ? payload.data : payload) as T; } export type AdminUser = { id: string; username: string; slug: string; email: string; displayName: string; role: string; status: string; balanceCents: number; createdAt: string; }; export type AdminSummary = { totalUsers: number; activeUsers: number; totalBalanceCents: number; llm?: { keyCount: number; selectedKeyName: string | null; globalModel: string | null; }; }; export type LedgerEntry = { id: string; userId: string; username?: string; type: string; amountCents: number; note: string; createdAt: string; }; export type UsageRecord = { id: string; userId: string; username?: string; provider: string; model: string; inputTokens: number; outputTokens: number; costCents: number; createdAt: string; }; export type WechatAdminSummary = { config: { mpEnabled: boolean; scheduleEnabled: boolean; reminderWorkerEnabled: boolean; appId: string | null; publicBaseUrl: string | null; bindPath: string | null; tokenEndpointConfigured: boolean; customerServiceEndpointConfigured: boolean; }; counts: { boundUsers: number; routes: { total: number; active: number }; recentMessages: Record; digests: Record; recentDeliveries: Record; }; }; export type WechatBinding = { userId: string; username: string; displayName: string; status: string; appId: string | null; openidMasked: string; nickname: string | null; avatarUrl: string | null; lastLoginAt: number; boundAt: number; routeId: string | null; routeStatus: string | null; agentSessionId: string | null; routeUpdatedAt: number | null; }; export type WechatMessage = { appId: string | null; openidMasked: string; msgId: string; status: string; agentSessionId: string | null; createdAt: number; updatedAt: number; userId: string | null; username: string | null; displayName: string | null; }; export type WechatDigestSubscription = { id: string; userId: string; username: string; displayName: string; digestType: string; hour: number; minute: number; timezone: string; channel: string; status: string; nextRunAt: number; lastRunAt: number | null; attempts: number; lastError: string | null; sourceText: string | null; createdAt: number; updatedAt: number; }; export type WechatDeliveryLog = { id: string; reminderId: string | null; subscriptionId: string | null; userId: string; username: string; displayName: string; channel: string; status: string; providerMessageId: string | null; errorCode: string | null; errorMessage: string | null; createdAt: number; digestType: string | null; }; export type WechatWebNotification = { id: string; userId: string; username: string; displayName: string; channel: string; notificationType: string; title: string; body: string; status: string; readAt: number | null; createdAt: number; updatedAt: number; }; export type CreateWechatNotificationPayload = { userId?: string; userIds?: string[]; audience?: 'all'; allUsers?: boolean; title: string; body: string; notificationType?: string; channel?: 'web' | 'wechat'; channels?: Array<'web' | 'wechat'>; }; export type SystemDisclosureMode = 'off' | 'shadow' | 'enforce'; export type SystemDisclosurePolicyConfig = { enabled: boolean; mode: SystemDisclosureMode; refusalText: string; productNames: string[]; selfReferences: string[]; categories: Record; }; export type SystemDisclosurePolicyState = { config: SystemDisclosurePolicyConfig; policyVersion: number; updatedBy: string | null; updatedAt: number | null; source: string; refreshedAt?: number | null; lastRefreshError?: string | null; }; export type OrchestratorMode = 'off' | 'shadow' | 'canary' | 'active'; export type OrchestratorConfig = { mode: OrchestratorMode; primaryEngine: string; fallbackEngine: string; serviceUrl: string; requestTimeoutMs: number; rolloutPercent: number; userAllowlist: string[]; workflowAllowlist: string[]; fallbackToNative: boolean; requireHealthy: boolean; }; export type OrchestratorRuntime = { killSwitch: boolean; configured: boolean; effective: boolean; reason: string | null; executesLangGraph: boolean; plansLangGraph: boolean; shadowsLangGraph: boolean; executionHandoff: { implemented: boolean; requested: boolean; enabled: boolean; reason: string | null; }; }; export type OrchestratorEngineDescriptor = { id: string; label: string; kind: string; configured: boolean; capabilities: string[]; }; export type OrchestratorExecutorDescriptor = { id: string; label: string; kind: string; enabled: boolean; dispatchImplemented: boolean; status: string; capabilities: string[]; }; export type OrchestratorConfigState = { config: OrchestratorConfig; configVersion: number; updatedBy: string | null; updatedAt: number | null; source: string; runtime: OrchestratorRuntime; engines: OrchestratorEngineDescriptor[]; executors: OrchestratorExecutorDescriptor[]; }; export type OrchestratorServiceHealth = { checkedAt: number; ok: boolean; status: 'healthy' | 'unhealthy' | 'unconfigured' | 'timeout' | 'unreachable' | 'fetch_unavailable'; latencyMs: number; httpStatus: number | null; details: { service: string | null; checkpoint: { kind?: string; durable?: boolean } | null; execution: string | null; executorGateway: { dispatchImplemented: boolean; executionEnabled: boolean; store: { kind: string | null; durable: boolean } | null; } | null; } | null; }; export type OrchestratorRuntimeState = OrchestratorConfigState & { serviceHealth: OrchestratorServiceHealth; }; export type OrchestratorShadowMetrics = { observations: number; successes: number; failures: number; successRate: number | null; failureRate: number | null; latencyP50Ms: number | null; latencyP95Ms: number | null; nativeSucceeded: number; nativeFailed: number; lastObservedAt: number | null; sampled: boolean; }; export type OrchestratorShadowRun = { eventId: string; runId: string; requestId: string; userId: string; sessionId: string | null; nativeStatus: string; nativeAttempts: number; shadowStatus: 'succeeded' | 'failed'; engine: string; configVersion: number | null; phase: string | null; taskType: string | null; synthetic: boolean; executorAdapter: string | null; latencyMs: number | null; error: { code: string; message: string } | null; observedAt: number; nativeCompletedAt: number | null; }; export type OrchestratorShadowRunList = { generatedAt: number; window: { hours: number; from: number }; metrics: OrchestratorShadowMetrics; runs: OrchestratorShadowRun[]; }; export type OrchestratorExecutionPlan = { eventId: string; runId: string; requestId: string; userId: string; sessionId: string | null; nativeStatus: string; nativeAttempts: number; mode: string | null; candidateEngine: string; effectiveEngine: string; fallbackEngine: string; reason: string | null; candidateReason: string | null; configVersion: number | null; bucket: number | null; taskType: string | null; dryRun: boolean; handoffAllowed: boolean; plannedAt: number; nativeCompletedAt: number | null; }; export type OrchestratorExecutionPlanMetrics = { decisions: number; candidateSelections: number; candidateSelectionRate: number | null; nativeSelections: number; nativeSucceeded: number; nativeFailed: number; nativeSettledRate: number | null; handoffAllowed: number; distinctSessions: number; lastPlannedAt: number | null; candidateReasons: Array<{ value: string; count: number }>; taskTypes: Array<{ value: string; count: number }>; sampled: boolean; }; export type OrchestratorExecutionPlanList = { generatedAt: number; window: { hours: number; from: number }; metrics: OrchestratorExecutionPlanMetrics; plans: OrchestratorExecutionPlan[]; }; export type OrchestratorCanaryReadinessCheck = { id: string; passed: boolean; actual: string | number | boolean | null; target: string | number | boolean | null; }; export type OrchestratorCanaryReadiness = { generatedAt: number; ready: boolean; recommendation: 'keep_shadow' | 'manual_canary_review'; window: { hours: number; from: number }; thresholds: { hours: number; minObservations: number; minSuccessRate: number; maxP95LatencyMs: number; minLatencyCoverageRate: number; minNativeSettledRate: number; minDistinctSessions: number; maxHoursSinceLastObservation: number; }; samples: { totalObservations: number; eligibleObservations: number; excludedSynthetic: number; successes: number; failures: number; successRate: number | null; latencyCoverageRate: number | null; latencyP95Ms: number | null; nativeSettledRate: number | null; distinctSessions: number; lastObservedAt: number | null; hoursSinceLastObservation: number | null; sampled: boolean; }; service: { status: string | null; latencyMs: number | null; checkpointKind: string | null; checkpointDurable: boolean | null; execution: string | null; }; checks: OrchestratorCanaryReadinessCheck[]; blockers: string[]; failureCodes: Array<{ code: string; count: number }>; }; export type OrchestratorShadowRunDetail = { native: { runId: string; requestId: string; userId: string; sessionId: string | null; status: string; attempts: number; error: string | null; createdAt: number; updatedAt: number; startedAt: number | null; completedAt: number | null; }; localEvents: Array<{ eventId: string; type: string; data: Record | null; createdAt: number; }>; remote: { available: boolean; state: { status?: string; phase?: string; plan?: Record; result?: Record; } | null; events: Array<{ sequence?: number; type?: string; timestamp?: number; data?: Record | null; }>; error: { code: string; message: string } | null; }; }; // ─── Summary ────────────────────────────────────────────────────────────────── export async function fetchAdminSummary() { return adminFetch<{ summary: AdminSummary }>('/admin-api/summary'); } // ─── Trust & Policy ─────────────────────────────────────────────────────────── export async function fetchSystemDisclosurePolicy() { return adminFetch('/admin-api/system-disclosure-policy/config'); } export async function updateSystemDisclosurePolicy(config: SystemDisclosurePolicyConfig) { return adminFetch('/admin-api/system-disclosure-policy/config', { method: 'PUT', body: JSON.stringify({ config }), }); } // ─── Workflow Orchestrator ─────────────────────────────────────────────────── export async function fetchOrchestratorConfig() { return adminFetch('/admin-api/orchestrator/config'); } export async function updateOrchestratorConfig(config: OrchestratorConfig) { return adminFetch('/admin-api/orchestrator/config', { method: 'PUT', body: JSON.stringify({ config }), }); } export async function fetchOrchestratorRuntime() { return adminFetch('/admin-api/orchestrator/runtime'); } export async function fetchOrchestratorShadowRuns(params: { hours?: number; limit?: number; status?: 'all' | 'succeeded' | 'failed'; } = {}) { const query = new URLSearchParams(); if (params.hours) query.set('hours', String(params.hours)); if (params.limit) query.set('limit', String(params.limit)); if (params.status) query.set('status', params.status); return adminFetch( `/admin-api/orchestrator/shadow-runs?${query}`, ); } export async function fetchOrchestratorExecutionPlans(params: { hours?: number; limit?: number; selection?: 'all' | 'candidate' | 'native'; } = {}) { const query = new URLSearchParams(); if (params.hours) query.set('hours', String(params.hours)); if (params.limit) query.set('limit', String(params.limit)); if (params.selection) query.set('selection', params.selection); return adminFetch( `/admin-api/orchestrator/execution-plans?${query}`, ); } export async function fetchOrchestratorCanaryReadiness() { return adminFetch( '/admin-api/orchestrator/canary-readiness', ); } export async function fetchOrchestratorShadowRun(runId: string) { return adminFetch( `/admin-api/orchestrator/shadow-runs/${encodeURIComponent(runId)}`, ); } // ─── Users ──────────────────────────────────────────────────────────────────── export async function fetchAdminUsers(params: { page?: number; pageSize?: number; search?: string; role?: string; status?: string; }) { const q = new URLSearchParams(); if (params.page) q.set('page', String(params.page)); if (params.pageSize) q.set('pageSize', String(params.pageSize)); if (params.search) q.set('search', params.search); if (params.role) q.set('role', params.role); if (params.status) q.set('status', params.status); return adminFetch<{ users: AdminUser[]; total: number; page: number; pageSize: number; totalPages: number }>( `/admin-api/users?${q}`, ); } export async function createAdminUser(body: { username: string; password: string; displayName?: string; role?: string; }) { return adminFetch<{ user: AdminUser }>('/admin-api/users', { method: 'POST', body: JSON.stringify(body), }); } export async function patchAdminUser( userId: string, patch: { role?: string; status?: string; displayName?: string; password?: string }, ) { return adminFetch<{ user: AdminUser }>(`/admin-api/users/${userId}`, { method: 'PATCH', body: JSON.stringify(patch), }); } export async function rechargeUser(userId: string, amountCents: number, note = '') { return adminFetch<{ user: AdminUser }>(`/admin-api/users/${userId}/recharge`, { method: 'POST', body: JSON.stringify({ amountCents, note }), }); } // ─── Billing ────────────────────────────────────────────────────────────────── export async function fetchAdminLedger(params: { page?: number; pageSize?: number } = {}) { const q = new URLSearchParams(); if (params.page) q.set('page', String(params.page)); if (params.pageSize) q.set('pageSize', String(params.pageSize)); return adminFetch<{ entries: LedgerEntry[]; total: number; page: number; totalPages: number }>( `/admin-api/ledger?${q}`, ); } export async function fetchAdminUsage(params: { page?: number; pageSize?: number } = {}) { const q = new URLSearchParams(); if (params.page) q.set('page', String(params.page)); if (params.pageSize) q.set('pageSize', String(params.pageSize)); return adminFetch<{ records: UsageRecord[]; total: number; page: number; totalPages: number }>( `/admin-api/usage?${q}`, ); } // ─── WeChat MP ──────────────────────────────────────────────────────────────── export async function fetchWechatSummary() { return adminFetch('/admin-api/wechat/summary'); } export async function fetchWechatBindings(params: { search?: string; limit?: number } = {}) { const q = new URLSearchParams(); if (params.search) q.set('search', params.search); if (params.limit) q.set('limit', String(params.limit)); return adminFetch<{ bindings: WechatBinding[] }>(`/admin-api/wechat/bindings?${q}`); } export async function fetchWechatMessages(params: { status?: string; limit?: number } = {}) { const q = new URLSearchParams(); if (params.status) q.set('status', params.status); if (params.limit) q.set('limit', String(params.limit)); return adminFetch<{ messages: WechatMessage[] }>(`/admin-api/wechat/messages?${q}`); } export async function fetchWechatDigests(params: { status?: string; limit?: number } = {}) { const q = new URLSearchParams(); if (params.status) q.set('status', params.status); if (params.limit) q.set('limit', String(params.limit)); return adminFetch<{ digests: WechatDigestSubscription[] }>(`/admin-api/wechat/digests?${q}`); } export async function fetchWechatDeliveries(params: { status?: string; limit?: number } = {}) { const q = new URLSearchParams(); if (params.status) q.set('status', params.status); if (params.limit) q.set('limit', String(params.limit)); return adminFetch<{ deliveries: WechatDeliveryLog[] }>(`/admin-api/wechat/deliveries?${q}`); } export async function fetchWechatWebNotifications(params: { status?: string; limit?: number } = {}) { const q = new URLSearchParams(); if (params.status) q.set('status', params.status); if (params.limit) q.set('limit', String(params.limit)); return adminFetch<{ notifications: WechatWebNotification[] }>( `/admin-api/wechat/web-notifications?${q}`, ); } export async function createWechatWebNotification(body: CreateWechatNotificationPayload) { return adminFetch<{ ok: boolean; created: number; wechatSent: number; wechatFailures: Array<{ userId: string; message: string }>; targets: number; }>('/admin-api/wechat/web-notifications', { method: 'POST', body: JSON.stringify(body), }); } export async function clearWechatRoute(userId: string) { return adminFetch<{ ok: boolean; deleted: number; openidMasked: string }>( `/admin-api/wechat/users/${userId}/route/clear`, { method: 'POST' }, ); } export async function cancelWechatDigest(id: string) { return adminFetch<{ ok: boolean }>(`/admin-api/wechat/digests/${id}/cancel`, { method: 'POST' }); } export async function resumeWechatDigest(id: string) { return adminFetch<{ ok: boolean; nextRunAt: number }>(`/admin-api/wechat/digests/${id}/resume`, { method: 'POST', }); }