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; executionEnabled: boolean; }; export type OrchestratorRuntime = { killSwitch: boolean; configured: boolean; effective: boolean; reason: string | null; executesLangGraph: boolean; plansLangGraph: boolean; shadowsLangGraph: boolean; shadowObservation: { requested: boolean; enabled: boolean; reason: string | null; environmentGate: boolean; }; pageDataValidationGate: { requested: boolean; enabled: boolean; failClosed: boolean; reason: string | null; environmentGate: boolean; }; executionHandoff: { implemented: boolean; requested: boolean; enabled: boolean; reason: string | null; environmentGate: boolean; }; }; 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; skipped: number; successRate: number | null; failureRate: number | null; skipRate: 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' | 'skipped'; 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; skipReason: 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; maxSkipRate: number; maxP95LatencyMs: number; minLatencyCoverageRate: number; minNativeSettledRate: number; minDistinctSessions: number; maxHoursSinceLastObservation: number; }; samples: { totalObservations: number; eligibleObservations: number; excludedSynthetic: number; successes: number; failures: number; skipped: number; successRate: number | null; skipRate: 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; executorJobStoreKind: string | null; executorJobStoreDurable: 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; validation?: { version?: string; kind?: string; taskType?: string | null; required?: boolean; verdict?: 'passed' | 'failed' | 'inconclusive'; checks?: Array<{ id?: string; status?: 'passed' | 'failed' | 'skipped'; codes?: string[]; }>; metrics?: { pageCount?: number; publicationCount?: number; }; source?: string; dataPolicy?: string; observedAt?: number; } | null; } | null; events: Array<{ sequence?: number; type?: string; timestamp?: number; data?: Record | null; }>; error: { code: string; message: string } | null; executorJob: { available: boolean; state: { version?: string; jobId?: string; executor?: string; status?: string; reason?: string; attempts?: number; createdAt?: number; updatedAt?: number; } | null; events: Array<{ version?: string; eventId?: string; jobId?: string; 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' | 'skipped'; } = {}) { 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', }); } // ─── Agent Code Run ─────────────────────────────────────────────────────────── export type AgentCodeRunConfigShape = { codeRun: { enabled: boolean; clientEnabled: boolean; generalAutodetect: boolean; requireValidation: boolean; userAllowlist: string[]; taskTypeAllowlist: string[]; }; pageDataDev: { autodetect: boolean; }; meta: { notes: string; }; }; export type AgentCodeRunAdminConfig = { config: AgentCodeRunConfigShape; updatedAt: number | null; updatedBy: string | null; source: string; envOverrideActive: boolean; }; export type AgentCodeRunRuntimeState = { source: string; updatedAt: number | null; updatedBy: string | null; config: AgentCodeRunConfigShape; policy: { source: string; enabled: boolean; clientEnabled: boolean; generalAutodetect: boolean; requireValidation: boolean; pageDataDevAutodetect: boolean; userAllowlist: string[]; taskTypeAllowlist: string[]; }; envOverrideActive: boolean; }; export async function fetchAgentCodeRunConfig() { return adminFetch('/admin-api/agent-code-run/config'); } export async function fetchAgentCodeRunRuntime() { return adminFetch('/admin-api/agent-code-run/runtime'); } export async function patchAgentCodeRunConfig(body: { config: AgentCodeRunConfigShape }) { return adminFetch('/admin-api/agent-code-run/config', { method: 'PATCH', body: JSON.stringify(body), }); } export type AgentCodeRunHistoryRow = { id: string; userId: string; requestId: string; status: string; taskType: string; executor: string | null; errorMessage: string | null; createdAt: number | null; updatedAt: number | null; }; export async function fetchAgentCodeRunHistory(limit = 50) { return adminFetch<{ runs: AgentCodeRunHistoryRow[]; limit: number }>( `/admin-api/agent-code-run/runs?limit=${limit}`, ); } // ─── Unified LLM Providers ──────────────────────────────────────────────────── export type LlmProviderKeyRow = { id: string; providerId: string; providerKind: 'builtin' | 'custom'; providerLabel: string; name: string; defaultModel: string; models: string[]; apiUrl: string | null; status: 'active' | 'disabled'; isSelected: boolean; apiKeyMasked: string; }; export type LlmGlobalSettings = { keyId: string | null; keyName: string | null; providerLabel: string | null; globalModel: string | null; availableModels: string[]; }; export type ChatIntentRouterAdminConfig = { enabled?: boolean; shadowMode?: boolean; canaryUserIds?: string; modelProviderKeyId?: string; model?: string; modelApiType?: string; minConfidence?: string; memoryResolveEnabled?: boolean; memoryResolveLimit?: string; timeoutMs?: string; fallbackRoute?: string; }; export type MemoryV2AdminConfigState = { config: { chatIntentRouter?: ChatIntentRouterAdminConfig; [key: string]: unknown; }; updatedAt: number | null; updatedBy: string | null; }; export type MemoryV2RuntimeState = { source: string; updatedAt: number | null; updatedBy: string | null; fingerprint?: string; overrides: Record; }; export async function fetchLlmProviderKeys() { return adminFetch<{ keys: LlmProviderKeyRow[] }>('/admin-api/llm-providers/keys'); } export async function fetchLlmGlobalSettings() { return adminFetch<{ global: LlmGlobalSettings }>('/admin-api/llm-providers/global'); } export async function fetchMemoryV2Config() { return adminFetch('/admin-api/memory-v2/config'); } export async function patchMemoryV2Config(patch: Record) { return adminFetch('/admin-api/memory-v2/config', { method: 'PATCH', body: JSON.stringify(patch), }); } export async function fetchMemoryV2Runtime() { return adminFetch('/admin-api/memory-v2/runtime'); } export type GoalRunRuntimeState = { enabled: boolean; canaryUserIds: string[]; canaryMode: boolean; }; export type GoalRunSummaryResponse = { counts: Record; runtime: GoalRunRuntimeState; }; export type GoalRunListItem = { id: string; userId: string; username: string | null; title: string; intentSummary: string; status: string; priority: number; sourceChannel: string; sourceSessionId: string | null; currentCheckpointId: string | null; checkpointCount: number; activeAgentRunCount: number; createdAt: number; updatedAt: number; completedAt: number | null; }; export type GoalRunCheckpointRow = { id: string; goalRunId: string; sequence: number; title: string; description: string | null; status: string; agentRunId: string | null; outputSummary: string | null; userFeedback: string | null; approvedAt: number | null; createdAt: number; updatedAt: number; startedAt: number | null; completedAt: number | null; }; export type GoalRunAgentRunRow = { id: string; status: string; requestId: string; goalCheckpointId: string | null; createdAt: number; updatedAt: number; completedAt: number | null; }; export type GoalRunDetail = { id: string; userId: string; username: string | null; title: string; intentSummary: string; status: string; priority: number; sourceChannel: string; sourceSessionId: string | null; sourceMessageId: string | null; currentCheckpointId: string | null; context: unknown; memorySnapshot: unknown; createdAt: number; updatedAt: number; completedAt: number | null; checkpoints: GoalRunCheckpointRow[]; agentRuns: GoalRunAgentRunRow[]; canaryEnabled: boolean; }; export async function fetchGoalRunSummary() { return adminFetch('/admin-api/goal-runs/summary'); } export async function fetchGoalRuns(params?: { status?: string; userId?: string; limit?: number; offset?: number; }) { const query = new URLSearchParams(); if (params?.status) query.set('status', params.status); if (params?.userId) query.set('userId', params.userId); if (params?.limit != null) query.set('limit', String(params.limit)); if (params?.offset != null) query.set('offset', String(params.offset)); const suffix = query.toString() ? `?${query.toString()}` : ''; return adminFetch<{ items: GoalRunListItem[]; status: string | null; limit: number; offset: number }>( `/admin-api/goal-runs${suffix}`, ); } export async function fetchGoalRunDetail(goalRunId: string) { return adminFetch<{ goal: GoalRunDetail }>(`/admin-api/goal-runs/${encodeURIComponent(goalRunId)}`); } export async function approveGoalRunCheckpoint( goalRunId: string, checkpointId: string, feedback?: string | null, ) { return adminFetch<{ ok: true; goal: GoalRunDetail }>( `/admin-api/goal-runs/${encodeURIComponent(goalRunId)}/checkpoints/${encodeURIComponent(checkpointId)}/approve`, { method: 'POST', body: JSON.stringify({ feedback: feedback ?? null }), }, ); } export async function cancelGoalRun(goalRunId: string) { return adminFetch<{ ok: true; goal: GoalRunDetail }>( `/admin-api/goal-runs/${encodeURIComponent(goalRunId)}/cancel`, { method: 'POST', body: JSON.stringify({}) }, ); }