| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- // 后台 API:同源 Cookie 鉴权,复用 AgentClient 的请求与错误处理
- import { AgentClient, BotClient } from '@agent-server/client'
- import type {
- ApiKeyDto, ApiKeyProfiles, BotDto, BotInput, BotKeyDto, ApiReferenceDto, AppSettings, BrowserTokenDto, CodexPluginCatalogDto, CodexPluginDetailDto, CodexPluginInstallResultDto, CodexSkillCatalogDto, CommunitySearchResultDto, CustomSkillDetailDto, CustomSkillDto, FileDiffDto, SkillScanDto, SkillSourceDto, SkillSourceInput, CommunitySkillDto, CommunitySkillPreviewDto, DeepPartial, FileAccessPolicy, FsEntryDto, FsRootDto, FsShortcutDto, KnowledgeRootDto, McpServerDto, McpServerInput, McpStateDto, McpTestResultDto, McpToolPolicy, PathCheck, PluginAction, PluginStateDto, ProfileDto, ProfileInput, ProfileVersionDto,
- ProviderQuotaDto, ProviderStatusDto, RecentErrorDto, ResolvedAccessDto, SkillInfo, UsageSummaryDto,
- } from '@agent-server/shared'
- export const client = new AgentClient({ baseUrl: '' })
- /** Bot Playground:以后台身份调用 /bot/v1(ADR-0024) */
- export const botClient = new BotClient({ baseUrl: '' })
- const req = <T>(method: string, path: string, body?: unknown) => client.request<T>(method, `/admin/v1${path}`, body)
- const fsq = (root: string, path: string) => `root=${encodeURIComponent(root)}&path=${encodeURIComponent(path)}`
- export interface AuditRow { id: number; ts: string; actor: string; sessionId: string | null; kind: string; summary: string; detail: unknown }
- export type KnowledgeInput = Pick<KnowledgeRootDto, 'name' | 'path' | 'description' | 'access' | 'enabled'>
- export const admin = {
- me: () => req<{ loggedIn: boolean }>('GET', '/auth/me'),
- login: (password: string) => req<{ ok: true }>('POST', '/auth/login', { password }),
- logout: () => req<{ ok: true }>('POST', '/auth/logout'),
- changePassword: (current: string, next: string) => req<{ ok: true }>('POST', '/auth/password', { current, next }),
- claudeLogin: {
- current: () => req<{ id: string; url: string; expiresAt: string } | null>('GET', '/providers/claude/login'),
- start: (body: { sso?: boolean; email?: string }) => req<{ id: string; url: string; expiresAt: string }>('POST', '/providers/claude/login', body),
- submit: (id: string, code: string) => req<{ ok: boolean; message: string; retry?: boolean }>('POST', `/providers/claude/login/${id}/code`, { code }),
- cancel: (id: string) => req<void>('DELETE', `/providers/claude/login/${id}`),
- logout: () => req<{ ok: true }>('POST', '/providers/claude/logout'),
- },
- codexLogin: {
- current: () => req<CodexLoginState | null>('GET', '/providers/codex/login'),
- start: () => req<CodexLoginState>('POST', '/providers/codex/login'),
- cancel: () => req<void>('DELETE', '/providers/codex/login'),
- logout: () => req<{ ok: true }>('POST', '/providers/codex/logout'),
- },
- providerQuota: (id: string) => req<ProviderQuotaDto>('GET', `/providers/${id}/quota`),
- apiReference: () => req<ApiReferenceDto>('GET', '/api-reference.json'),
- apiKeyDebugToken: (id: string) => req<BrowserTokenDto>('POST', `/api-keys/${id}/debug-token`),
- apiReferenceMeta: () => req<{ pdfAvailable: boolean }>('GET', '/api-reference/meta'),
- usage: (days = 30) => req<UsageSummaryDto>('GET', `/usage?days=${days}`),
- usageErrors: (limit = 10) => req<RecentErrorDto[]>('GET', `/usage/errors?limit=${limit}`),
- overview: () => req<{ providers: ProviderStatusDto[]; runs: { running: number; queued: number; active: number }; version: string }>('GET', '/overview'),
- settings: () => req<{ settings: AppSettings; defaults: AppSettings; builtinGlobalDeny: string[] }>('GET', '/settings'),
- updateSettings: (patch: DeepPartial<AppSettings>) => req<{ settings: AppSettings }>('PATCH', '/settings', patch),
- checkPath: (path: string, purpose: 'knowledge' | 'generic' = 'knowledge') => req<PathCheck>('POST', '/paths/check', { path, purpose }),
- skills: (engine: 'claude' | 'codex' = 'claude') => req<SkillInfo[]>('GET', `/skills?engine=${engine}`),
- codexSkills: {
- catalog: () => req<CodexSkillCatalogDto>('GET', '/codex-skills'),
- refresh: () => req<CodexSkillCatalogDto>('POST', '/codex-skills/refresh'),
- install: (name: string) => req<CodexSkillCatalogDto>('POST', `/codex-skills/${encodeURIComponent(name)}`),
- uninstall: (name: string) => req<CodexSkillCatalogDto>('DELETE', `/codex-skills/${encodeURIComponent(name)}`),
- },
- codexPlugins: {
- catalog: (q: { q?: string; category?: string; installed?: boolean; offset?: number; refresh?: boolean }) => req<CodexPluginCatalogDto>('GET', `/codex-plugins?${new URLSearchParams({
- q: q.q ?? '', category: q.category ?? '', installed: q.installed ? '1' : '', offset: String(q.offset ?? 0), refresh: q.refresh ? '1' : '',
- })}`),
- detail: (name: string) => req<CodexPluginDetailDto>('GET', `/codex-plugins/${encodeURIComponent(name)}`),
- install: (name: string) => req<CodexPluginInstallResultDto>('POST', `/codex-plugins/${encodeURIComponent(name)}/install`),
- uninstall: (name: string) => req<{ ok: true }>('POST', `/codex-plugins/${encodeURIComponent(name)}/uninstall`),
- },
- fs: {
- roots: () => req<{ roots: FsRootDto[]; shortcuts: FsShortcutDto[] }>('GET', '/fs/roots'),
- list: (root: string, path: string) => req<{ entries: FsEntryDto[]; truncated: boolean; sessionTitle: string | null }>('GET', `/fs/list?${fsq(root, path)}`),
- readText: (root: string, path: string) => req<{ content: string; mtime: string }>('GET', `/fs/text?${fsq(root, path)}`),
- writeText: (root: string, path: string, content: string, baseMtime: string | null) => req<{ mtime: string }>('PUT', `/fs/text?${fsq(root, path)}`, { content, baseMtime }),
- mkdir: (root: string, path: string) => req<{ ok: true }>('POST', `/fs/mkdir?${fsq(root, path)}`),
- move: (root: string, path: string, to: string) => req<{ ok: true }>('POST', `/fs/move?${fsq(root, path)}`, { to }),
- remove: (root: string, path: string) => req<{ ok: true }>('DELETE', `/fs/entry?${fsq(root, path)}`),
- upload: (root: string, dir: string, files: File[]) => {
- const form = new FormData()
- for (const f of files) form.append('file', f, f.name)
- return req<FsEntryDto[]>('POST', `/fs/upload?${fsq(root, dir)}`, form)
- },
- downloadUrl: (root: string, path: string, inline = false) => `/admin/v1/fs/download?${fsq(root, path)}${inline ? '&inline=1' : ''}`,
- zipUrl: (root: string, path: string) => `/admin/v1/fs/zip?${fsq(root, path)}`,
- },
- customSkills: {
- list: () => req<CustomSkillDto[]>('GET', '/custom-skills'),
- create: (name: string, description: string) => req<CustomSkillDetailDto>('POST', '/custom-skills', { name, description }),
- get: (id: string) => req<CustomSkillDetailDto>('GET', `/custom-skills/${id}`),
- setFlags: (id: string, flags: { enabled?: boolean; includeInAll?: boolean }) => req<CustomSkillDetailDto>('PATCH', `/custom-skills/${id}`, flags),
- remove: (id: string) => req<void>('DELETE', `/custom-skills/${id}`),
- fileUrl: (id: string, path: string, version?: number) => `/admin/v1/custom-skills/${id}/file?path=${encodeURIComponent(path)}${version === undefined ? '' : `&version=${version}`}`,
- readText: async (id: string, path: string, version?: number) => {
- const r = await fetch(`/admin/v1/custom-skills/${id}/file?path=${encodeURIComponent(path)}${version === undefined ? '' : `&version=${version}`}`, { credentials: 'same-origin' })
- if (!r.ok) throw new Error((await r.json().catch(() => null))?.error?.message ?? `HTTP ${r.status}`)
- return r.text()
- },
- writeText: (id: string, path: string, content: string) => req<CustomSkillDetailDto>('PUT', `/custom-skills/${id}/file?path=${encodeURIComponent(path)}`, { content }),
- upload: (id: string, path: string, file: File) => {
- const form = new FormData()
- form.append('file', file, file.name)
- return req<CustomSkillDetailDto>('PUT', `/custom-skills/${id}/file?path=${encodeURIComponent(path)}`, form)
- },
- deleteFile: (id: string, path: string) => req<CustomSkillDetailDto>('DELETE', `/custom-skills/${id}/file?path=${encodeURIComponent(path)}`),
- move: (id: string, from: string, to: string) => req<CustomSkillDetailDto>('POST', `/custom-skills/${id}/move`, { from, to }),
- discard: (id: string) => req<CustomSkillDetailDto>('POST', `/custom-skills/${id}/discard`),
- save: (id: string, baseVersion: number, publish: boolean) => req<CustomSkillDetailDto>('POST', `/custom-skills/${id}/save`, { baseVersion, publish }),
- publish: (id: string, version: number) => req<CustomSkillDetailDto>('POST', `/custom-skills/${id}/publish`, { version }),
- restore: (id: string, version: number) => req<CustomSkillDetailDto>('POST', `/custom-skills/${id}/versions/${version}/restore`),
- diff: (id: string, from: number | 'working', to: number | 'working') => req<FileDiffDto[]>('GET', `/custom-skills/${id}/diff?from=${from}&to=${to}`),
- exportUrl: (id: string, version?: number) => `/admin/v1/custom-skills/${id}/export${version === undefined ? '' : `?version=${version}`}`,
- detach: (id: string) => req<CustomSkillDetailDto>('POST', `/custom-skills/${id}/detach`),
- },
- skillSources: {
- list: () => req<SkillSourceDto[]>('GET', '/skill-sources'),
- create: (input: SkillSourceInput) => req<SkillSourceDto>('POST', '/skill-sources', input),
- update: (id: string, input: SkillSourceInput) => req<SkillSourceDto>('PUT', `/skill-sources/${id}`, input),
- remove: (id: string) => req<void>('DELETE', `/skill-sources/${id}`),
- scan: (id: string) => req<SkillScanDto>('POST', `/skill-sources/${id}/scan`),
- import: (id: string, skills: string[], digest: string) => req<{ imported: { name: string; version: number; created: boolean }[]; scan: SkillScanDto }>('POST', `/skill-sources/${id}/import`, { skills, digest }),
- upload: (file: File) => {
- const form = new FormData()
- form.append('file', file, file.name)
- return req<{ source: SkillSourceDto; scan: SkillScanDto }>('POST', '/skill-sources/upload', form)
- },
- },
- mcp: {
- state: () => req<McpStateDto>('GET', '/mcp'),
- create: (input: McpServerInput) => req<McpServerDto>('POST', '/mcp/servers', input),
- update: (id: string, input: McpServerInput) => req<McpServerDto>('PUT', `/mcp/servers/${id}`, input),
- remove: (id: string) => req<void>('DELETE', `/mcp/servers/${id}`),
- refresh: (id: string) => req<McpServerDto>('POST', `/mcp/servers/${id}/refresh`),
- setToolPolicy: (id: string, tool: string, policy: McpToolPolicy) => req<McpServerDto>('PUT', `/mcp/servers/${id}/tools/${encodeURIComponent(tool)}`, { policy }),
- test: (input: McpServerInput, id: string | null) => req<McpTestResultDto>('POST', '/mcp/test', { input, id }),
- },
- communitySkills: {
- list: () => req<CommunitySkillDto[]>('GET', '/community-skills'),
- search: (q: string) => req<CommunitySearchResultDto[]>('GET', `/community-skills/search?q=${encodeURIComponent(q)}`),
- preview: (source: string, skillId: string) => req<CommunitySkillPreviewDto>('POST', '/community-skills/preview', { source, skillId }),
- install: (source: string, skillId: string, sha: string) => req<CommunitySkillDto[]>('POST', '/community-skills/install', { source, skillId, sha }),
- uninstall: (name: string) => req<CommunitySkillDto[]>('DELETE', `/community-skills/${encodeURIComponent(name)}`),
- },
- plugins: (refresh = false) => req<PluginStateDto>('GET', `/plugins${refresh ? '?refresh=1' : ''}`),
- pluginAction: (a: PluginAction) => req<{ message: string; state: PluginStateDto }>('POST', '/plugins/actions', a),
- profiles: () => req<ProfileDto[]>('GET', '/profiles'),
- createProfile: (p: ProfileInput) => req<ProfileDto>('POST', '/profiles', p),
- updateProfile: (id: string, p: ProfileInput) => req<ProfileDto>('PUT', `/profiles/${id}`, p),
- deleteProfile: (id: string) => req<void>('DELETE', `/profiles/${id}`),
- profileVersions: (id: string) => req<ProfileVersionDto[]>('GET', `/profiles/${id}/versions`),
- restoreProfile: (id: string, version: number) => req<ProfileDto>('POST', `/profiles/${id}/versions/${version}/restore`),
- previewAccess: (fileAccess: FileAccessPolicy, knowledgeRootIds: string[]) => req<ResolvedAccessDto>('POST', '/access/preview', { fileAccess, knowledgeRootIds }),
- knowledge: () => req<KnowledgeRootDto[]>('GET', '/knowledge'),
- createKnowledge: (k: KnowledgeInput) => req<KnowledgeRootDto>('POST', '/knowledge', k),
- updateKnowledge: (id: string, k: KnowledgeInput) => req<KnowledgeRootDto>('PUT', `/knowledge/${id}`, k),
- deleteKnowledge: (id: string) => req<void>('DELETE', `/knowledge/${id}`),
- bots: () => req<BotDto[]>('GET', '/bots'),
- createBot: (input: BotInput) => req<BotDto>('POST', '/bots', input),
- updateBot: (id: string, input: BotInput) => req<BotDto>('PUT', `/bots/${id}`, input),
- deleteBot: (id: string) => req<void>('DELETE', `/bots/${id}`),
- botKeys: () => req<BotKeyDto[]>('GET', '/bot-keys'),
- createBotKey: (name: string, allowedBotIds: string[]) => req<{ key: string; botKey: BotKeyDto }>('POST', '/bot-keys', { name, allowedBotIds }),
- setBotKeyEnabled: (id: string, enabled: boolean) => req<{ ok: true }>('PATCH', `/bot-keys/${id}`, { enabled }),
- setBotKeyBots: (id: string, allowedBotIds: string[]) => req<{ ok: true }>('PUT', `/bot-keys/${id}/bots`, { allowedBotIds }),
- deleteBotKey: (id: string) => req<void>('DELETE', `/bot-keys/${id}`),
- botKeyDebugToken: (id: string) => req<BrowserTokenDto>('POST', `/bot-keys/${id}/debug-token`),
- apiKeys: () => req<ApiKeyDto[]>('GET', '/api-keys'),
- createApiKey: (name: string, access: ApiKeyProfiles) => req<{ key: string; apiKey: ApiKeyDto }>('POST', '/api-keys', { name, ...access }),
- setApiKeyProfiles: (id: string, access: ApiKeyProfiles) => req<{ ok: true }>('PUT', `/api-keys/${id}/profiles`, access),
- setApiKeyEnabled: (id: string, enabled: boolean) => req<{ ok: true }>('PATCH', `/api-keys/${id}`, { enabled }),
- deleteApiKey: (id: string) => req<void>('DELETE', `/api-keys/${id}`),
- audit: (q: { kind?: string; sessionId?: string; limit?: number } = {}) => {
- const s = new URLSearchParams(Object.entries(q).filter(([, v]) => v !== undefined && v !== '').map(([k, v]) => [k, String(v)]))
- return req<AuditRow[]>('GET', `/audit${s.size ? `?${s}` : ''}`)
- },
- }
- export interface CodexLoginState { loginId: string; verificationUrl: string; userCode: string; status: 'pending' | 'success' | 'failed' | 'cancelled'; error: string | null }
- export const errorMessage = (err: unknown) => (err instanceof Error ? err.message : String(err))
|