Files
browser/platform_web/api/client.ts
T

843 lines
35 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type {
AiProviderListResponse,
AiProviderModelsResponse,
AiProviderRequest,
AiProviderResponse,
AiProviderStatusRequest,
AiProviderTestResponse,
AiProviderUpdateRequest,
AIInvocationRequest,
AIInvocationResponse,
AIConfigDiffApprovalResponse,
AIConfigDiffListResponse,
AlertListResponse,
AlertResponse,
AlertRetryResponse,
ApiErrorResponse,
ArtifactContentChunk,
ArtifactDownloadReferenceResponse,
ArtifactFilterRequest,
ArtifactListResponse,
AuthSessionResponse,
AuditEventListResponse,
ClientManagerBuildRequest,
ClientManagerControlRequest,
ClientManagerDeployRequest,
ClientManagerDistributionResponse,
ClientManagerDownloadRequest,
ClientManagerInstallationListResponse,
ClientManagerInstallationResponse,
ClientManagerRetryRequest,
ClientManagerRevokeSessionRequest,
ClientManagerUninstallRequest,
ClientManagerUpdateRequest,
ComponentKeyResponse,
ComponentKeyResetRequest,
CurrentUserResponse,
DependencyCatalogResponse,
DependencyJobRequest,
DeclaredFileReadSnapshotResponse,
FileOperationDispatchRequest,
FileOperationDispatchResponse,
GameClientBridgeCancelRequest,
GameClientBridgeCancelResponse,
GameClientBridgeCommandFilterRequest,
GameClientBridgeCommandListResponse,
GameClientBridgeCommandResponse,
GameClientBridgeQueueRequest,
GameClientBridgeSnapshotListResponse,
GameClientBridgeSnapshotQuery,
GameClientBridgeStatusResponse,
GamePluginListResponse,
HealthResponse,
JobCreateRequest,
JobListResponse,
JobResponse,
LlmConfigSuggestionRequest,
LlmConfigSuggestionResponse,
LogBackfillRequest,
LogStreamEventOptions,
LogStreamCursorRequest,
LogStreamCursorResponse,
LogStreamListResponse,
LoginRequest,
MarketplacePluginFilterRequest,
MarketplacePluginListResponse,
MarketplacePluginResponse,
MarketplacePluginStateRequest,
PlatformResourceUsageResponse,
ProductionCapacitySummaryResponse,
CapacityAdmissionDecisionResponse,
PluginLifecycleActionRequest,
PluginLifecycleActionResponse,
PluginLifecycleListResponse,
PluginBridgeAuthorizeRequest,
PluginBridgeAuthorizeResponse,
PluginBridgeExecuteRequest,
PluginBridgeExecuteResponse,
RegisterRequest,
RunDistributionGenerateRequest,
RunDistributionResponse,
RunEndpointListResponse,
RunUpdateJobResponse,
RunUpdateJobListResponse,
RunUpdateRequest,
RuntimeBindingResponse,
RuntimeBindingUpdateRequest,
ServerConfigResponse,
ServerConfigDiffPreviewRequest,
ServerConfigDiffPreviewResponse,
ServerLifecycleCommandRequest,
ServerLifecycleCreateRequest,
ServerLifecycleResponse,
ServerDeploymentRequest,
ServerDeploymentRevealResponse,
ServerDeploymentResponse,
ServerConfigWriteApprovalRequest,
ServerConfigWriteDispatchResponse,
SourceRCONCommandRequest,
SourceRCONCommandResponse,
ServerInstanceListResponse,
ServerDeletionRequest,
ServerInstanceUpdateRequest,
ServerInstanceResponse,
ServerMemberListResponse,
ServerMemberRequest,
ServerMetricsListResponse,
MetricSampleListResponse,
BackupListResponse,
BackupResponse,
RemoteAdapterDeclarationListResponse,
RemoteAdapterRequest,
RemoteAdapterResponse,
ServerRuntimeActionsResponse,
UserCreateRequest,
UserListResponse,
UserProfileUpdateRequest,
UserResponse,
UserThemePreferenceRequest,
UserThemePreferenceResponse,
UserUpdateRequest
} from "./types";
import { readWebRuntimeEnv } from "../schemas/env";
import { parseSafeDependencyCatalog, parseSafeRunUpdate, parseSafeRunUpdateList } from "../schemas/runtimeUpdates";
import { parseSafeClientManagerLifecycle, parseSafeClientManagerLifecycleList } from "../schemas/clientManagerLifecycle";
import {
parseSafeGameClientBridgeCancellation,
parseSafeGameClientBridgeCommand,
parseSafeGameClientBridgeCommandList,
parseSafeGameClientBridgeSnapshotList,
parseSafeGameClientBridgeStatus
} from "../schemas/gameClientBridge";
import { safeDiagnosticText } from "../utils/safeDiagnosticText";
let platformApiSessionToken: string | null = null;
let platformApiAuthFailureHandler: ((error: PlatformApiError) => void) | null = null;
export function setPlatformApiSessionToken(token: string | null) {
platformApiSessionToken = token;
}
export function setPlatformApiAuthFailureHandler(handler: ((error: PlatformApiError) => void) | null) {
platformApiAuthFailureHandler = handler;
}
export class PlatformApiError extends Error {
constructor(
readonly status: number,
readonly code: string,
message: string
) {
super(message);
this.name = "PlatformApiError";
}
}
export class PlatformApiClient {
constructor(private readonly baseUrl = "/api/v1", private readonly sessionTokenProvider: () => string | null = () => platformApiSessionToken) {}
async health(): Promise<HealthResponse> {
return this.request<HealthResponse>("/healthz", { absolute: true });
}
async listGamePlugins(): Promise<GamePluginListResponse> {
return this.request<GamePluginListResponse>("/game-plugins");
}
async listMarketplacePlugins(filter: MarketplacePluginFilterRequest = {}): Promise<MarketplacePluginListResponse> {
return this.request<MarketplacePluginListResponse>(`/plugin-marketplace/plugins${marketplaceQuery(filter)}`);
}
async getMarketplacePlugin(id: string): Promise<MarketplacePluginResponse> {
return this.request<MarketplacePluginResponse>(`/plugin-marketplace/plugins/${encodeURIComponent(id)}`);
}
async setMarketplacePluginState(id: string, request: MarketplacePluginStateRequest): Promise<MarketplacePluginResponse> {
return this.request<MarketplacePluginResponse>(`/plugin-marketplace/plugins/${encodeURIComponent(id)}/state`, {
method: "POST",
body: request
});
}
async listServerInstances(): Promise<ServerInstanceListResponse> {
return this.request<ServerInstanceListResponse>("/server-instances");
}
async createServerWorkflow(request: ServerLifecycleCreateRequest): Promise<ServerLifecycleResponse> {
return this.request<ServerLifecycleResponse>("/server-instances/workflows/create", {
method: "POST",
body: request
});
}
async getServerDeployment(id: string): Promise<ServerDeploymentResponse> {
return this.request<ServerDeploymentResponse>(`/server-instances/${encodeURIComponent(id)}/deployment`);
}
async revealServerDeployment(id: string): Promise<ServerDeploymentRevealResponse> {
return this.request<ServerDeploymentRevealResponse>(`/server-instances/${encodeURIComponent(id)}/deployment/reveal`);
}
async updateServerDeployment(id: string, request: ServerDeploymentRequest): Promise<ServerDeploymentResponse> {
return this.request<ServerDeploymentResponse>(`/server-instances/${encodeURIComponent(id)}/deployment`, { method: "PUT", body: request });
}
async deployServerInstance(id: string, request: ServerLifecycleCommandRequest): Promise<ServerLifecycleResponse> {
return this.request<ServerLifecycleResponse>(`/server-instances/${encodeURIComponent(id)}/deploy`, { method: "POST", body: request });
}
async getServerRuntimeBinding(id: string): Promise<RuntimeBindingResponse> {
return this.request<RuntimeBindingResponse>(`/server-instances/${encodeURIComponent(id)}/runtime-binding`);
}
async updateServerRuntimeBinding(id: string, request: RuntimeBindingUpdateRequest): Promise<RuntimeBindingResponse> {
return this.request<RuntimeBindingResponse>(`/server-instances/${encodeURIComponent(id)}/runtime-binding`, { method: "PUT", body: request });
}
async startServerInstance(id: string, request: ServerLifecycleCommandRequest): Promise<ServerLifecycleResponse> {
return this.request<ServerLifecycleResponse>(`/server-instances/${encodeURIComponent(id)}/start`, {
method: "POST",
body: request
});
}
async stopServerInstance(id: string, request: ServerLifecycleCommandRequest): Promise<ServerLifecycleResponse> {
return this.request<ServerLifecycleResponse>(`/server-instances/${encodeURIComponent(id)}/stop`, {
method: "POST",
body: request
});
}
async queryServerProcessStatus(id: string, request: ServerLifecycleCommandRequest): Promise<ServerLifecycleResponse> {
return this.request<ServerLifecycleResponse>(`/server-instances/${encodeURIComponent(id)}/process/status`, {
method: "POST",
body: request
});
}
async listServerAdministratorCandidates(id: string): Promise<ServerMemberListResponse> {
return this.request<ServerMemberListResponse>(`/server-instances/${encodeURIComponent(id)}/administrators/candidates`);
}
async addServerAdministrator(id: string, request: ServerMemberRequest): Promise<ServerInstanceResponse> {
return this.request<ServerInstanceResponse>(`/server-instances/${encodeURIComponent(id)}/administrators`, {
method: "POST",
body: request
});
}
async removeServerAdministrator(id: string, userId: string): Promise<ServerInstanceResponse> {
return this.request<ServerInstanceResponse>(`/server-instances/${encodeURIComponent(id)}/administrators/${encodeURIComponent(userId)}`, {
method: "DELETE"
});
}
async listRunEndpoints(): Promise<RunEndpointListResponse> {
return this.request<RunEndpointListResponse>("/run/endpoints");
}
async listJobs(serverInstanceId?: string): Promise<JobListResponse> {
const params = serverInstanceId ? `?serverInstanceId=${encodeURIComponent(serverInstanceId)}` : "";
return this.request<JobListResponse>(`/jobs${params}`);
}
async listArtifacts(filter: ArtifactFilterRequest = {}): Promise<ArtifactListResponse> {
return this.request<ArtifactListResponse>(`/artifacts${artifactQuery(filter)}`);
}
async openArtifactDownload(id: string): Promise<ArtifactDownloadReferenceResponse> {
return this.request<ArtifactDownloadReferenceResponse>(`/artifacts/${encodeURIComponent(id)}/download`, { method: "POST", body: {} });
}
async readArtifactContent(id: string, offset = 0, limit?: number): Promise<ArtifactContentChunk> {
const params = new URLSearchParams({ offset: String(offset) });
if (limit !== undefined) {
params.set("limit", String(limit));
}
const headers = new Headers();
const sessionToken = this.sessionTokenProvider();
if (sessionToken) {
headers.set("Authorization", `Bearer ${sessionToken}`);
}
const response = await fetch(`${this.baseUrl}/artifacts/${encodeURIComponent(id)}/content?${params.toString()}`, { headers, credentials: "same-origin" });
if (!response.ok) {
throw await responseError(response);
}
const payload = await response.arrayBuffer();
return {
artifactId: response.headers.get("X-Artifact-Id") ?? undefined,
payload,
contentType: response.headers.get("Content-Type") ?? "application/octet-stream",
contentLength: Number(response.headers.get("Content-Length") ?? payload.byteLength),
contentRange: response.headers.get("Content-Range") ?? undefined,
checksum: response.headers.get("X-Artifact-Checksum") ?? undefined,
contentChecksum: response.headers.get("X-Artifact-Content-Checksum") ?? undefined,
storageBehavior: response.headers.get("X-Artifact-Storage") ?? undefined
};
}
async getJob(id: string): Promise<JobResponse> {
return this.request<JobResponse>(`/jobs/${encodeURIComponent(id)}`);
}
async getServerRuntimeActions(id: string): Promise<ServerRuntimeActionsResponse> {
return this.request<ServerRuntimeActionsResponse>(`/server-instances/${encodeURIComponent(id)}/runtime/actions`);
}
async generateRunDistribution(id: string, request: RunDistributionGenerateRequest): Promise<RunDistributionResponse> {
return this.request<RunDistributionResponse>(`/server-instances/${encodeURIComponent(id)}/run/generate`, {
method: "POST",
body: request
});
}
async downloadLatestRunDistribution(id: string): Promise<ArtifactDownloadReferenceResponse> {
return this.request<ArtifactDownloadReferenceResponse>(`/server-instances/${encodeURIComponent(id)}/run/download`, {
method: "POST",
body: {}
});
}
async resetRunKey(id: string): Promise<ComponentKeyResponse> {
return this.request<ComponentKeyResponse>(`/server-instances/${encodeURIComponent(id)}/run/key/reset`, {
method: "POST",
body: {}
});
}
async pushRunUpdate(id: string, request: RunUpdateRequest): Promise<RunUpdateJobResponse> {
const response = await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/run/update`, {
method: "POST",
body: request
});
return parseSafeRunUpdate(response);
}
async listRunUpdates(id: string): Promise<RunUpdateJobListResponse> {
return parseSafeRunUpdateList(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/run/update`));
}
async generateClientManager(id: string, request: ClientManagerBuildRequest): Promise<ClientManagerDistributionResponse> {
return this.request<ClientManagerDistributionResponse>(`/server-instances/${encodeURIComponent(id)}/client-managers/generate`, {
method: "POST",
body: request
});
}
async downloadLatestClientManager(id: string, request: ClientManagerDownloadRequest = {}): Promise<ArtifactDownloadReferenceResponse> {
return this.request<ArtifactDownloadReferenceResponse>(`/server-instances/${encodeURIComponent(id)}/client-managers/download`, {
method: "POST",
body: request
});
}
async resetClientManagerKey(id: string, request: ComponentKeyResetRequest): Promise<ComponentKeyResponse> {
return this.request<ComponentKeyResponse>(`/server-instances/${encodeURIComponent(id)}/client-managers/key/reset`, {
method: "POST",
body: request
});
}
async listClientManagerLifecycles(id: string): Promise<ClientManagerInstallationListResponse> {
return parseSafeClientManagerLifecycleList(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/client-managers`));
}
async getClientManagerLifecycle(id: string, profileKey: string): Promise<ClientManagerInstallationResponse> {
return parseSafeClientManagerLifecycle(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/client-managers/${encodeURIComponent(profileKey)}`));
}
async deployClientManager(id: string, request: ClientManagerDeployRequest): Promise<ClientManagerInstallationResponse> {
return parseSafeClientManagerLifecycle(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/client-managers/deploy`, { method: "POST", body: request }));
}
async controlClientManager(id: string, request: ClientManagerControlRequest): Promise<ClientManagerInstallationResponse> {
return parseSafeClientManagerLifecycle(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/client-managers/control`, { method: "POST", body: request }));
}
async updateClientManager(id: string, request: ClientManagerUpdateRequest): Promise<ClientManagerInstallationResponse> {
return parseSafeClientManagerLifecycle(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/client-managers/update`, { method: "POST", body: request }));
}
async retryClientManagerLifecycle(id: string, request: ClientManagerRetryRequest): Promise<ClientManagerInstallationResponse> {
return parseSafeClientManagerLifecycle(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/client-managers/retry`, { method: "POST", body: request }));
}
async revokeClientManagerSession(id: string, request: ClientManagerRevokeSessionRequest): Promise<ClientManagerInstallationResponse> {
return parseSafeClientManagerLifecycle(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/client-managers/revoke-session`, { method: "POST", body: request }));
}
async uninstallClientManager(id: string, request: ClientManagerUninstallRequest): Promise<ClientManagerInstallationResponse> {
return parseSafeClientManagerLifecycle(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/client-managers/uninstall`, { method: "POST", body: request }));
}
async getGameClientBridgeStatus(id: string): Promise<GameClientBridgeStatusResponse> {
return parseSafeGameClientBridgeStatus(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/game-client-bridge`));
}
async listGameClientBridgeCommands(id: string, filter: GameClientBridgeCommandFilterRequest = {}): Promise<GameClientBridgeCommandListResponse> {
const params = new URLSearchParams();
if (filter.profileKey) params.set("profileKey", filter.profileKey);
if (filter.state) params.set("state", filter.state);
if (filter.commandType) params.set("commandType", filter.commandType);
const query = params.toString();
return parseSafeGameClientBridgeCommandList(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/game-client-bridge/commands${query ? `?${query}` : ""}`));
}
async queueGameClientBridgeCommand(id: string, request: GameClientBridgeQueueRequest): Promise<GameClientBridgeCommandResponse> {
return parseSafeGameClientBridgeCommand(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/game-client-bridge/commands`, { method: "POST", body: request }));
}
async getGameClientBridgeCommand(id: string, commandId: string): Promise<GameClientBridgeCommandResponse> {
return parseSafeGameClientBridgeCommand(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/game-client-bridge/commands/${encodeURIComponent(commandId)}`));
}
async cancelGameClientBridgeCommand(id: string, commandId: string, request: GameClientBridgeCancelRequest = {}): Promise<GameClientBridgeCancelResponse> {
return parseSafeGameClientBridgeCancellation(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/game-client-bridge/commands/${encodeURIComponent(commandId)}/cancel`, { method: "POST", body: request }));
}
async listGameClientBridgeSnapshots(id: string, query: GameClientBridgeSnapshotQuery = {}): Promise<GameClientBridgeSnapshotListResponse> {
const params = new URLSearchParams();
if (query.profileKey) params.set("profileKey", query.profileKey);
if (query.type) params.set("type", query.type);
if (query.streamKey) params.set("streamKey", query.streamKey);
if (query.observedAfter) params.set("observedAfter", query.observedAfter);
if (query.limit !== undefined) params.set("limit", String(query.limit));
const search = params.toString();
return parseSafeGameClientBridgeSnapshotList(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/game-client-bridge/snapshots${search ? `?${search}` : ""}`));
}
async checkDependencies(id: string, request: DependencyJobRequest): Promise<JobResponse> {
return this.request<JobResponse>(`/server-instances/${encodeURIComponent(id)}/dependencies/check`, {
method: "POST",
body: request
});
}
async getDependencyCatalog(id: string): Promise<DependencyCatalogResponse> {
return parseSafeDependencyCatalog(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/dependencies`));
}
async installDependencies(id: string, request: DependencyJobRequest): Promise<JobResponse> {
return this.request<JobResponse>(`/server-instances/${encodeURIComponent(id)}/dependencies/install`, {
method: "POST",
body: request
});
}
async listServerLiveLogs(id: string): Promise<LogStreamListResponse> {
return this.request<LogStreamListResponse>(`/server-instances/${encodeURIComponent(id)}/logs/live`);
}
openServerLogEvents(id: string, options: LogStreamEventOptions = {}): EventSource {
return new EventSource(this.serverLogEventsUrl(id, options), { withCredentials: true });
}
serverLogEventsUrl(id: string, options: LogStreamEventOptions = {}): string {
const params = new URLSearchParams();
if (options.historyLimit !== undefined) params.set("historyLimit", String(options.historyLimit));
const query = params.toString();
return `${this.baseUrl}/server-instances/${encodeURIComponent(id)}/logs/events${query ? `?${query}` : ""}`;
}
async requestLogBackfill(id: string, request: LogBackfillRequest): Promise<JobResponse> {
return this.request<JobResponse>(`/server-instances/${encodeURIComponent(id)}/logs/backfill`, {
method: "POST",
body: request
});
}
async createJob(request: JobCreateRequest): Promise<JobResponse> {
return this.request<JobResponse>("/jobs", { method: "POST", body: request });
}
async register(request: RegisterRequest): Promise<AuthSessionResponse> {
return this.request<AuthSessionResponse>("/auth/register", { method: "POST", body: request });
}
async login(request: LoginRequest): Promise<AuthSessionResponse> {
return this.request<AuthSessionResponse>("/auth/login", { method: "POST", body: request });
}
async logout(): Promise<void> {
await this.request<void>("/auth/logout", { method: "POST", parseJson: false });
}
async getCurrentUser(): Promise<CurrentUserResponse> {
return this.request<CurrentUserResponse>("/users/current");
}
async listUsers(): Promise<UserListResponse> {
return this.request<UserListResponse>("/users");
}
async createUser(request: UserCreateRequest): Promise<UserResponse> {
return this.request<UserResponse>("/users", { method: "POST", body: request });
}
async updateUser(id: string, request: UserUpdateRequest): Promise<UserResponse> {
return this.request<UserResponse>(`/users/${encodeURIComponent(id)}`, { method: "PUT", body: request });
}
async updateCurrentUserProfile(request: UserProfileUpdateRequest): Promise<CurrentUserResponse> {
return this.request<CurrentUserResponse>("/users/current/profile", { method: "PUT", body: request });
}
async updateCurrentUserTheme(request: UserThemePreferenceRequest): Promise<UserThemePreferenceResponse> {
return this.request<UserThemePreferenceResponse>("/users/current/theme", { method: "PUT", body: request });
}
async getServerInstance(id: string): Promise<ServerInstanceResponse> {
return this.request<ServerInstanceResponse>(`/server-instances/${encodeURIComponent(id)}`);
}
async updateServerInstance(id: string, request: ServerInstanceUpdateRequest): Promise<ServerInstanceResponse> {
return this.request<ServerInstanceResponse>(`/server-instances/${encodeURIComponent(id)}`, {
method: "PUT",
body: request
});
}
async deleteServerInstance(id: string, request: ServerDeletionRequest): Promise<void> {
return this.request<void>(`/server-instances/${encodeURIComponent(id)}`, { method: "DELETE", body: request, parseJson: false });
}
async getPlatformResourceUsage(): Promise<PlatformResourceUsageResponse> {
return this.request<PlatformResourceUsageResponse>("/metrics/platform");
}
async getProductionCapacity(): Promise<ProductionCapacitySummaryResponse> {
return this.request<ProductionCapacitySummaryResponse>("/production/capacity");
}
async checkCapacityAdmission(request: { serverInstanceId?: string; runEndpointId?: string; capability: string; targetKey?: string; idempotencyKey?: string }): Promise<CapacityAdmissionDecisionResponse> {
return this.request<CapacityAdmissionDecisionResponse>("/production/capacity/admission", { method: "POST", body: request });
}
async listAlerts(filter: { state?: string; sourceKind?: string; sourceId?: string; severity?: string } = {}): Promise<AlertListResponse> {
const params = new URLSearchParams();
Object.entries(filter).forEach(([key, value]) => { if (value) params.set(key, value); });
const query = params.toString();
return this.request<AlertListResponse>(`/alerts${query ? `?${query}` : ""}`);
}
async acknowledgeAlert(id: string, note = ""): Promise<AlertResponse> {
return this.request<AlertResponse>(`/alerts/${encodeURIComponent(id)}/acknowledge`, { method: "POST", body: { note } });
}
async resolveAlert(id: string, note = ""): Promise<AlertResponse> {
return this.request<AlertResponse>(`/alerts/${encodeURIComponent(id)}/resolve`, { method: "POST", body: { note } });
}
async retryAlert(id: string, idempotencyKey: string): Promise<AlertRetryResponse> {
return this.request<AlertRetryResponse>(`/alerts/${encodeURIComponent(id)}/retry`, { method: "POST", body: { idempotencyKey } });
}
async listPluginLifecycles(filter: { pluginId?: string; serverInstanceId?: string; currentState?: string } = {}): Promise<PluginLifecycleListResponse> {
const params = new URLSearchParams();
Object.entries(filter).forEach(([key, value]) => { if (value) params.set(key, value); });
const query = params.toString();
return this.request<PluginLifecycleListResponse>(`/plugin-lifecycles${query ? `?${query}` : ""}`);
}
async runPluginLifecycle(pluginId: string, request: PluginLifecycleActionRequest): Promise<PluginLifecycleActionResponse> {
return this.request<PluginLifecycleActionResponse>(`/plugin-lifecycles/${encodeURIComponent(pluginId)}/actions`, { method: "POST", body: request });
}
async listAIConfigDiffs(filter: { serverInstanceId?: string; pluginId?: string; state?: string } = {}): Promise<AIConfigDiffListResponse> {
const params = new URLSearchParams();
Object.entries(filter).forEach(([key, value]) => { if (value) params.set(key, value); });
const query = params.toString();
return this.request<AIConfigDiffListResponse>(`/ai/config-diffs${query ? `?${query}` : ""}`);
}
async approveAIConfigDiff(id: string, idempotencyKey: string): Promise<AIConfigDiffApprovalResponse> {
return this.request<AIConfigDiffApprovalResponse>(`/ai/config-diffs/${encodeURIComponent(id)}/approve`, { method: "POST", body: { idempotencyKey } });
}
async listServerMetrics(): Promise<ServerMetricsListResponse> {
return this.request<ServerMetricsListResponse>("/metrics/server-instances");
}
async listMetricHistory(serverInstanceId: string, limit = 100): Promise<MetricSampleListResponse> {
const params = new URLSearchParams({ serverInstanceId, limit: String(limit) });
return this.request<MetricSampleListResponse>(`/metrics/server-instances/history?${params.toString()}`);
}
async listBackups(serverInstanceId: string): Promise<BackupListResponse> {
return this.request<BackupListResponse>(`/backups?serverInstanceId=${encodeURIComponent(serverInstanceId)}`);
}
async getBackup(id: string): Promise<BackupResponse> {
return this.request<BackupResponse>(`/backups/${encodeURIComponent(id)}`);
}
async listRemoteAdapters(serverInstanceId: string): Promise<RemoteAdapterDeclarationListResponse> {
return this.request<RemoteAdapterDeclarationListResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/remote-adapters`);
}
async requestRemoteAdapter(serverInstanceId: string, request: RemoteAdapterRequest): Promise<RemoteAdapterResponse> {
return this.request<RemoteAdapterResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/remote-adapters`, { method: "POST", body: request });
}
async sendSourceRCONCommand(serverInstanceId: string, request: SourceRCONCommandRequest): Promise<SourceRCONCommandResponse> {
return this.request<SourceRCONCommandResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/rcon/commands`, { method: "POST", body: request });
}
async getServerConfig(id: string): Promise<ServerConfigResponse> {
return this.request<ServerConfigResponse>(`/server-instances/${encodeURIComponent(id)}/config`);
}
async previewServerConfigDiff(id: string, request: ServerConfigDiffPreviewRequest): Promise<ServerConfigDiffPreviewResponse> {
return this.request<ServerConfigDiffPreviewResponse>(`/server-instances/${encodeURIComponent(id)}/config/diff`, {
method: "POST",
body: request
});
}
async approveServerConfigWrite(id: string, request: ServerConfigWriteApprovalRequest): Promise<ServerConfigWriteDispatchResponse> {
return this.request<ServerConfigWriteDispatchResponse>(`/server-instances/${encodeURIComponent(id)}/config/approve`, {
method: "POST",
body: request
});
}
async dispatchFileOperation(request: FileOperationDispatchRequest): Promise<FileOperationDispatchResponse> {
return this.request<FileOperationDispatchResponse>("/file-operations/dispatch", {
method: "POST",
body: request
});
}
async getDeclaredFileReadSnapshot(serverInstanceId: string, fileKey: string): Promise<DeclaredFileReadSnapshotResponse> {
const query = new URLSearchParams({ key: fileKey });
return this.request<DeclaredFileReadSnapshotResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/files/read-snapshot?${query.toString()}`);
}
async listLogStreams(): Promise<LogStreamListResponse> {
return this.request<LogStreamListResponse>("/log-streams");
}
async queryLogStream(request: LogStreamCursorRequest): Promise<LogStreamCursorResponse> {
return this.request<LogStreamCursorResponse>("/log-streams/query", { method: "POST", body: request });
}
async listAuditEvents(): Promise<AuditEventListResponse> {
return this.request<AuditEventListResponse>("/audit-events");
}
async suggestServerConfig(request: LlmConfigSuggestionRequest): Promise<LlmConfigSuggestionResponse> {
return this.request<LlmConfigSuggestionResponse>("/ai/config-suggestions", { method: "POST", body: request });
}
async invokeAI(request: AIInvocationRequest): Promise<AIInvocationResponse> {
return this.request<AIInvocationResponse>("/ai/invocations", { method: "POST", body: request });
}
async authorizePluginBridge(request: PluginBridgeAuthorizeRequest): Promise<PluginBridgeAuthorizeResponse> {
return this.request<PluginBridgeAuthorizeResponse>("/plugin-bridge/authorize", {
method: "POST",
body: request
});
}
async executePluginBridge(request: PluginBridgeExecuteRequest): Promise<PluginBridgeExecuteResponse> {
return this.request<PluginBridgeExecuteResponse>("/plugin-bridge/execute", {
method: "POST",
body: request
});
}
async listAiProviders(): Promise<AiProviderListResponse> {
return this.request<AiProviderListResponse>("/ai-providers");
}
async createAiProvider(request: AiProviderRequest): Promise<AiProviderResponse> {
return this.request<AiProviderResponse>("/ai-providers", {
method: "POST",
body: request
});
}
async updateAiProvider(id: string, request: AiProviderUpdateRequest): Promise<AiProviderResponse> {
return this.request<AiProviderResponse>(`/ai-providers/${encodeURIComponent(id)}`, {
method: "PUT",
body: request
});
}
async setAiProviderStatus(id: string, request: AiProviderStatusRequest): Promise<AiProviderResponse> {
return this.request<AiProviderResponse>(`/ai-providers/${encodeURIComponent(id)}/status`, {
method: "POST",
body: request
});
}
async testAiProvider(id: string): Promise<AiProviderTestResponse> {
return this.request<AiProviderTestResponse>(`/ai-providers/${encodeURIComponent(id)}/test`, {
method: "POST"
});
}
async listAiProviderModels(id: string): Promise<AiProviderModelsResponse> {
return this.request<AiProviderModelsResponse>(`/ai-providers/${encodeURIComponent(id)}/models`);
}
private async request<T>(path: string, options: ApiRequestOptions = {}): Promise<T> {
const headers = new Headers(options.init?.headers);
if (options.body !== undefined) {
headers.set("Content-Type", "application/json");
}
const sessionToken = this.sessionTokenProvider();
if (sessionToken && !headers.has("Authorization")) {
headers.set("Authorization", `Bearer ${sessionToken}`);
}
const response = await fetch(options.absolute ? path : `${this.baseUrl}${path}`, {
...options.init,
credentials: options.init?.credentials ?? "same-origin",
method: options.method ?? options.init?.method ?? "GET",
headers,
body: options.body === undefined ? options.init?.body : JSON.stringify(options.body)
});
if (!response.ok) {
throw await responseError(response);
}
if (options.parseJson === false || response.status === 204) {
return undefined as T;
}
return response.json() as Promise<T>;
}
}
interface ApiRequestOptions {
absolute?: boolean;
method?: string;
body?: unknown;
init?: RequestInit;
parseJson?: boolean;
}
async function safeReadError(response: Response): Promise<ApiErrorResponse | null> {
try {
return (await response.json()) as ApiErrorResponse;
} catch {
return null;
}
}
async function responseError(response: Response): Promise<PlatformApiError> {
const apiError = await safeReadError(response);
const message = response.status === 401
? "会话已失效,请重新登录。"
: apiError?.code === "validation_failed"
? safeValidationMessage(apiError)
: response.status === 403
? safeForbiddenMessage(apiError?.message)
: apiError?.message ?? `request failed: ${response.status}`;
const error = new PlatformApiError(response.status, apiError?.code ?? "request_failed", message);
if (response.status === 401) {
platformApiSessionToken = null;
platformApiAuthFailureHandler?.(error);
}
return error;
}
function safeValidationMessage(apiError?: ApiErrorResponse | null): string {
const details = apiError?.details
?.map((detail) => safeValidationDetail(detail))
.filter((detail): detail is string => Boolean(detail));
if (details?.length) {
return details.slice(0, 3).join("");
}
const sanitized = safeDiagnosticText(apiError?.message, "")?.trim();
return sanitized || "请求校验失败。";
}
function safeValidationDetail(detail: string): string | undefined {
const sanitized = safeDiagnosticText(detail, "")?.trim();
switch (sanitized) {
case "password is required":
return "请输入当前登录密码。";
case "running or installing server instances must be stopped before delete":
return "运行中或安装中的服务器必须先停止再删除。";
default:
return sanitized || undefined;
}
}
function safeForbiddenMessage(apiMessage?: string): string {
const sanitized = safeDiagnosticText(apiMessage, "")?.trim();
if (!sanitized || sanitized === "account is not allowed to access this resource") {
return "没有权限访问该资源。";
}
if (sanitized === "password confirmation failed") {
return "当前登录密码不正确,请重新输入。";
}
const missingPermission = sanitized.match(/^plugin does not declare required permission:\s*([a-z0-9._-]+)$/i);
if (missingPermission) {
return `插件未声明所需权限:${missingPermission[1]}`;
}
if (sanitized === "plugin is not installed") {
return "插件未安装,不能执行该操作。";
}
return "没有权限访问该资源。";
}
function marketplaceQuery(filter: MarketplacePluginFilterRequest): string {
const params = new URLSearchParams();
if (filter.status && filter.status !== "all") {
params.set("status", filter.status);
}
if (filter.serverType) {
params.set("serverType", filter.serverType);
}
if (filter.capability) {
params.set("capability", filter.capability);
}
if (filter.keyword) {
params.set("keyword", filter.keyword);
}
const query = params.toString();
return query ? `?${query}` : "";
}
function artifactQuery(filter: ArtifactFilterRequest): string {
const params = new URLSearchParams();
if (filter.ownerKind) {
params.set("ownerKind", filter.ownerKind);
}
if (filter.ownerId) {
params.set("ownerId", filter.ownerId);
}
if (filter.state) {
params.set("state", filter.state);
}
const query = params.toString();
return query ? `?${query}` : "";
}
export const platformApiClient = new PlatformApiClient(readWebRuntimeEnv().platformApiBaseUrl);