feat: 完整游戏运维功能
This commit is contained in:
+129
-12
@@ -1,14 +1,16 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { PlatformApiClient, setPlatformApiSessionToken } from "./client";
|
||||
import { PlatformApiClient, PlatformApiError, setPlatformApiAuthFailureHandler, setPlatformApiSessionToken } from "./client";
|
||||
import type { AiProviderResponse, ArtifactDownloadReferenceResponse, GamePluginResponse, JobResponse, MarketplacePluginResponse, RunEndpointResponse, ServerInstanceResponse } from "./types";
|
||||
|
||||
const runtimeDigest = `sha256:${"a".repeat(64)}`;
|
||||
|
||||
const provider: AiProviderResponse = {
|
||||
id: "ai.openai",
|
||||
name: "OpenAI",
|
||||
kind: "openai",
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
apiKeyRef: "secret://providers/openai",
|
||||
apiKeyConfigured: true,
|
||||
models: ["gpt-4.1"],
|
||||
defaultModel: "gpt-4.1",
|
||||
relayMode: "direct",
|
||||
@@ -33,6 +35,7 @@ const plugin: GamePluginResponse = {
|
||||
pages: [{ key: "logs", title: "Logs", path: "/logs", permissions: ["server.logs.read"], bridgeActions: ["logs.query"] }],
|
||||
tags: ["example"],
|
||||
aiPurposes: ["logs.diagnose"],
|
||||
runtimeProfiles: { lifecycleProfiles: [{ key: "local", mode: "local-process", capabilities: ["process.install", "process.start", "process.stop"] }] },
|
||||
status: "installed"
|
||||
};
|
||||
|
||||
@@ -90,6 +93,9 @@ const job: JobResponse = {
|
||||
idempotencyKey: "idem-start",
|
||||
state: "queued",
|
||||
progress: { percent: 0, message: "queued" },
|
||||
retryPolicy: { maxAttempts: 3, initialBackoffSeconds: 2, maxBackoffSeconds: 60 },
|
||||
attempt: 0,
|
||||
reconcileCount: 0,
|
||||
createdAt: "2026-07-03T00:00:00Z",
|
||||
updatedAt: "2026-07-03T00:00:00Z"
|
||||
};
|
||||
@@ -139,6 +145,7 @@ const runtimeDownload: ArtifactDownloadReferenceResponse = {
|
||||
describe("PlatformApiClient AI providers", () => {
|
||||
afterEach(() => {
|
||||
setPlatformApiSessionToken(null);
|
||||
setPlatformApiAuthFailureHandler(null);
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
@@ -170,8 +177,21 @@ describe("PlatformApiClient AI providers", () => {
|
||||
const client = new PlatformApiClient();
|
||||
|
||||
await expect(client.listAiProviders()).resolves.toMatchObject({ count: 1 });
|
||||
await expect(client.createAiProvider(provider)).resolves.toMatchObject({ id: provider.id });
|
||||
await expect(client.updateAiProvider(provider.id, { ...provider, name: "OpenAI Relay" })).resolves.toMatchObject({ name: "OpenAI Relay" });
|
||||
const providerRequest = {
|
||||
id: provider.id,
|
||||
name: provider.name,
|
||||
kind: provider.kind,
|
||||
baseUrl: provider.baseUrl,
|
||||
apiKeyRef: "secret://providers/openai",
|
||||
models: provider.models,
|
||||
defaultModel: provider.defaultModel,
|
||||
relayMode: provider.relayMode,
|
||||
timeoutMs: provider.timeoutMs,
|
||||
redactionPolicy: provider.redactionPolicy
|
||||
};
|
||||
await expect(client.createAiProvider(providerRequest)).resolves.toMatchObject({ id: provider.id });
|
||||
const { id: _id, ...providerUpdate } = providerRequest;
|
||||
await expect(client.updateAiProvider(provider.id, { ...providerUpdate, name: "OpenAI Relay" })).resolves.toMatchObject({ name: "OpenAI Relay" });
|
||||
await expect(client.setAiProviderStatus(provider.id, { status: "disabled" })).resolves.toMatchObject({ status: "disabled" });
|
||||
await expect(client.testAiProvider(provider.id)).resolves.toMatchObject({ success: true, mode: "metadata" });
|
||||
await expect(client.listAiProviderModels(provider.id)).resolves.toMatchObject({ models: ["gpt-4.1"] });
|
||||
@@ -343,6 +363,9 @@ describe("PlatformApiClient AI providers", () => {
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/stop") && init?.method === "POST") {
|
||||
return jsonResponse({ accepted: true, action: "stop", instance: server, job: { ...job, capability: "process.stop" } });
|
||||
}
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/process/status") && init?.method === "POST") {
|
||||
return jsonResponse({ accepted: true, action: "status", instance: server, job: { ...job, capability: "process.status", executionResult: { kind: "process", processState: "running", auditSummary: "private supervised process identity" } } });
|
||||
}
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/administrators/candidates") && (!init?.method || init.method === "GET")) {
|
||||
return jsonResponse({ items: [{ id: "user-2", displayName: "Helper", status: "active", roles: ["server-admin"] }], count: 1 });
|
||||
}
|
||||
@@ -356,6 +379,13 @@ describe("PlatformApiClient AI providers", () => {
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/runtime/actions")) {
|
||||
return jsonResponse(runtimeActions);
|
||||
}
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/runtime-binding") && (!init?.method || init.method === "GET")) {
|
||||
return jsonResponse({ serverInstanceId: server.id, pluginId: plugin.id, profileKey: "local", mode: "local-process", configured: true, keys: [{ key: "server-root", required: true, configured: true, secret: false }], missingKeys: [], status: "complete" });
|
||||
}
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/runtime-binding") && init?.method === "PUT") {
|
||||
expect(JSON.parse(String(init.body))).toEqual({ profileKey: "local", bindings: { "server-root": "runtime.server-root" } });
|
||||
return jsonResponse({ serverInstanceId: server.id, pluginId: plugin.id, profileKey: "local", mode: "local-process", configured: true, keys: [{ key: "server-root", required: true, configured: true, secret: false }], missingKeys: [], status: "complete" });
|
||||
}
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/run/generate") && init?.method === "POST") {
|
||||
expect(JSON.parse(String(init.body))).toEqual({ targetOs: "linux", targetArch: "amd64", idempotencyKey: "idem-run-generate" });
|
||||
return jsonResponse({
|
||||
@@ -392,20 +422,49 @@ describe("PlatformApiClient AI providers", () => {
|
||||
});
|
||||
}
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/run/update") && init?.method === "POST") {
|
||||
expect(JSON.parse(String(init.body))).toEqual({ artifactId: "artifact-run-1", checksum: "sha256:runchecksum", idempotencyKey: "idem-run-update" });
|
||||
expect(JSON.parse(String(init.body))).toEqual({ artifactId: "artifact-run-1", checksum: runtimeDigest, idempotencyKey: "idem-run-update" });
|
||||
return jsonResponse({
|
||||
id: "run-update-1",
|
||||
serverInstanceId: server.id,
|
||||
runEndpointId: endpoint.id,
|
||||
artifactId: "artifact-run-1",
|
||||
checksum: "sha256:runchecksum",
|
||||
checksum: runtimeDigest,
|
||||
targetOs: "linux",
|
||||
targetArch: "amd64",
|
||||
targetRelease: "run-dist-2",
|
||||
previousVersion: "0.1.0",
|
||||
jobId: "job-run-update",
|
||||
idempotencyKey: "idem-run-update",
|
||||
status: "queued",
|
||||
phase: "queued",
|
||||
rollback: false,
|
||||
createdAt: "2026-07-03T00:00:00Z",
|
||||
updatedAt: "2026-07-03T00:00:00Z"
|
||||
});
|
||||
}
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/run/update") && init?.method === "GET") {
|
||||
return jsonResponse({
|
||||
items: [{
|
||||
id: "run-update-1",
|
||||
serverInstanceId: server.id,
|
||||
runEndpointId: endpoint.id,
|
||||
artifactId: "artifact-run-1",
|
||||
checksum: runtimeDigest,
|
||||
targetOs: "linux",
|
||||
targetArch: "amd64",
|
||||
targetRelease: "run-dist-2",
|
||||
previousVersion: "0.1.0",
|
||||
jobId: "job-run-update",
|
||||
status: "running",
|
||||
phase: "restart-requested",
|
||||
message: "verified update staged; restart requested",
|
||||
rollback: false,
|
||||
createdAt: "2026-07-03T00:00:00Z",
|
||||
updatedAt: "2026-07-03T00:01:00Z"
|
||||
}],
|
||||
count: 1
|
||||
});
|
||||
}
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/client-managers/generate") && init?.method === "POST") {
|
||||
expect(JSON.parse(String(init.body))).toEqual({
|
||||
profileKey: "scum-client-manager",
|
||||
@@ -457,8 +516,21 @@ describe("PlatformApiClient AI providers", () => {
|
||||
expect(JSON.parse(String(init.body))).toEqual({ probeKey: "java-21", idempotencyKey: "idem-dep-check" });
|
||||
return jsonResponse({ ...job, id: "job-dep-check", capability: "dependencies.check", targetKey: "dependencies/java-21" });
|
||||
}
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/dependencies") && init?.method === "GET") {
|
||||
return jsonResponse({
|
||||
serverInstanceId: server.id,
|
||||
pluginId: plugin.id,
|
||||
pluginVersion: plugin.version,
|
||||
profileKey: "local",
|
||||
targetOs: "linux",
|
||||
targetArch: "amd64",
|
||||
probes: [{ key: "java-21", kind: "java.version", required: true, state: "missing", installPlanKey: "install-java-linux" }],
|
||||
plans: [{ key: "install-java-linux", title: "Install Java", targetOs: "linux", targetArch: "amd64", digest: runtimeDigest, steps: [{ type: "package", targetKey: "java", packageManager: "apt", packageName: "openjdk-21-jre" }] }],
|
||||
updatedAt: "2026-07-03T00:00:00Z"
|
||||
});
|
||||
}
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/dependencies/install") && init?.method === "POST") {
|
||||
expect(JSON.parse(String(init.body))).toEqual({ probeKey: "java-21", installPlanKey: "install-java-linux", idempotencyKey: "idem-dep-install" });
|
||||
expect(JSON.parse(String(init.body))).toEqual({ probeKey: "java-21", installPlanKey: "install-java-linux", planDigest: runtimeDigest, idempotencyKey: "idem-dep-install" });
|
||||
return jsonResponse({ ...job, id: "job-dep-install", capability: "dependencies.install", targetKey: "dependencies/install/install-java-linux" });
|
||||
}
|
||||
if (url.endsWith("/api/v1/server-instances/server-1/logs/live")) {
|
||||
@@ -546,11 +618,12 @@ describe("PlatformApiClient AI providers", () => {
|
||||
await expect(client.listArtifacts({ ownerKind: "job", ownerId: job.id, state: "available" })).resolves.toMatchObject({ count: 1, items: [{ id: artifact.id }] });
|
||||
await expect(client.openArtifactDownload(artifact.id)).resolves.toMatchObject({ downloadUrl: "/api/v1/artifacts/artifact-1/content", rangeSupported: true });
|
||||
await expect(client.readArtifactContent(artifact.id, 0, 8)).resolves.toMatchObject({ contentLength: 8, contentRange: "bytes 0-7/18", checksum: artifact.checksum });
|
||||
await expect(client.createServerWorkflow({ id: "server-2", pluginId: plugin.id, runEndpointId: endpoint.id, name: "Server 2", idempotencyKey: "idem-create" })).resolves.toMatchObject({
|
||||
await expect(client.createServerWorkflow({ id: "server-2", pluginId: plugin.id, runEndpointId: endpoint.id, name: "Server 2", idempotencyKey: "idem-create", profileKey: "local", bindings: {} })).resolves.toMatchObject({
|
||||
action: "create"
|
||||
});
|
||||
await expect(client.startServerInstance(server.id, { expectedConfigVersion: 1, idempotencyKey: "idem-start" })).resolves.toMatchObject({ action: "start" });
|
||||
await expect(client.stopServerInstance(server.id, { expectedConfigVersion: 1, idempotencyKey: "idem-stop" })).resolves.toMatchObject({ action: "stop" });
|
||||
await expect(client.queryServerProcessStatus(server.id, { expectedConfigVersion: 1, idempotencyKey: "idem-status" })).resolves.toMatchObject({ action: "status", job: { executionResult: { processState: "running" } } });
|
||||
await expect(client.listServerAdministratorCandidates(server.id)).resolves.toMatchObject({ count: 1 });
|
||||
await expect(client.addServerAdministrator(server.id, { userId: "user-2" })).resolves.toMatchObject({ adminUserIds: ["user-admin-1", "user-2"] });
|
||||
await expect(client.removeServerAdministrator(server.id, "user-2")).resolves.toMatchObject({ adminUserIds: [] });
|
||||
@@ -558,6 +631,8 @@ describe("PlatformApiClient AI providers", () => {
|
||||
const runtime = await client.getServerRuntimeActions(server.id);
|
||||
expect(runtime.runStatus).toBe("online");
|
||||
expect(runtime.actions.some((action) => action.key === "generate-run" && action.available)).toBe(true);
|
||||
await expect(client.getServerRuntimeBinding(server.id)).resolves.toMatchObject({ profileKey: "local", status: "complete", keys: [{ key: "server-root", configured: true }] });
|
||||
await expect(client.updateServerRuntimeBinding(server.id, { profileKey: "local", bindings: { "server-root": "runtime.server-root" } })).resolves.toMatchObject({ status: "complete" });
|
||||
await expect(client.generateRunDistribution(server.id, { targetOs: "linux", targetArch: "amd64", idempotencyKey: "idem-run-generate" })).resolves.toMatchObject({
|
||||
artifactId: "artifact-run-1",
|
||||
keyGeneration: 1,
|
||||
@@ -565,10 +640,11 @@ describe("PlatformApiClient AI providers", () => {
|
||||
});
|
||||
await expect(client.downloadLatestRunDistribution(server.id)).resolves.toMatchObject({ artifactId: "artifact-run-1", rangeSupported: true });
|
||||
await expect(client.resetRunKey(server.id)).resolves.toMatchObject({ componentKind: "run", generation: 2 });
|
||||
await expect(client.pushRunUpdate(server.id, { artifactId: "artifact-run-1", checksum: "sha256:runchecksum", idempotencyKey: "idem-run-update" })).resolves.toMatchObject({
|
||||
await expect(client.pushRunUpdate(server.id, { artifactId: "artifact-run-1", checksum: runtimeDigest, idempotencyKey: "idem-run-update" })).resolves.toMatchObject({
|
||||
jobId: "job-run-update",
|
||||
status: "queued"
|
||||
});
|
||||
await expect(client.listRunUpdates(server.id)).resolves.toMatchObject({ count: 1, items: [{ phase: "restart-requested", rollback: false }] });
|
||||
await expect(
|
||||
client.generateClientManager(server.id, {
|
||||
profileKey: "scum-client-manager",
|
||||
@@ -582,7 +658,8 @@ describe("PlatformApiClient AI providers", () => {
|
||||
await expect(client.downloadLatestClientManager(server.id, { profileKey: "scum-client-manager" })).resolves.toMatchObject({ artifactId: "artifact-client-1" });
|
||||
await expect(client.resetClientManagerKey(server.id, { componentKind: "client-manager", componentKey: "scum-client-manager" })).resolves.toMatchObject({ generation: 2 });
|
||||
await expect(client.checkDependencies(server.id, { probeKey: "java-21", idempotencyKey: "idem-dep-check" })).resolves.toMatchObject({ capability: "dependencies.check" });
|
||||
await expect(client.installDependencies(server.id, { probeKey: "java-21", installPlanKey: "install-java-linux", idempotencyKey: "idem-dep-install" })).resolves.toMatchObject({
|
||||
await expect(client.getDependencyCatalog(server.id)).resolves.toMatchObject({ targetOs: "linux", plans: [{ digest: runtimeDigest }] });
|
||||
await expect(client.installDependencies(server.id, { probeKey: "java-21", installPlanKey: "install-java-linux", planDigest: runtimeDigest, idempotencyKey: "idem-dep-install" })).resolves.toMatchObject({
|
||||
capability: "dependencies.install"
|
||||
});
|
||||
await expect(client.listServerLiveLogs(server.id)).resolves.toMatchObject({ count: 0 });
|
||||
@@ -599,7 +676,7 @@ describe("PlatformApiClient AI providers", () => {
|
||||
client.invokeAI({ requestId: "ai-1", serverInstanceId: server.id, purpose: "config.suggest", prompt: "Tune PVP safely", currentConfig: "server.name=Example Survival #1\n" })
|
||||
).resolves.toMatchObject({ status: "ok", usage: { mocked: true }, configRecommendation: { diffSummary: "review required" } });
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(38);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(43);
|
||||
});
|
||||
|
||||
it("calls plugin marketplace endpoints with filter and state contracts", async () => {
|
||||
@@ -651,7 +728,8 @@ describe("PlatformApiClient AI providers", () => {
|
||||
it("keeps raw key fields out of provider responses", () => {
|
||||
expect("apiKey" in provider).toBe(false);
|
||||
expect("rawApiKey" in provider).toBe(false);
|
||||
expect(provider.apiKeyRef).toBe("secret://providers/openai");
|
||||
expect(provider.apiKeyConfigured).toBe(true);
|
||||
expect("apiKeyRef" in provider).toBe(false);
|
||||
});
|
||||
|
||||
it("calls auth endpoints and attaches bearer sessions", async () => {
|
||||
@@ -687,6 +765,45 @@ describe("PlatformApiClient AI providers", () => {
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("resets 401 sessions and redacts auth error details", async () => {
|
||||
const onAuthFailure = vi.fn();
|
||||
setPlatformApiSessionToken("expired-session-token");
|
||||
setPlatformApiAuthFailureHandler(onAuthFailure);
|
||||
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({
|
||||
code: "unauthorized",
|
||||
message: "expired secret://internal/provider raw-token-value /srv/game unix:///tmp/run.sock"
|
||||
}), { status: 401, headers: { "Content-Type": "application/json" } })));
|
||||
|
||||
const client = new PlatformApiClient();
|
||||
const failure = await client.getCurrentUser().catch((error: unknown) => error);
|
||||
|
||||
expect(failure).toBeInstanceOf(PlatformApiError);
|
||||
expect(failure).toMatchObject({ status: 401, code: "unauthorized", message: "会话已失效,请重新登录。" });
|
||||
expect(String(failure)).not.toMatch(/secret:\/\/|raw-token-value|\/srv\/game|unix:\/\//);
|
||||
expect(onAuthFailure).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("keeps 403 as a safe capability denial without clearing the session", async () => {
|
||||
const onAuthFailure = vi.fn();
|
||||
setPlatformApiAuthFailureHandler(onAuthFailure);
|
||||
const fetchMock = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
|
||||
expect(new Headers(init?.headers).get("Authorization")).toBe("Bearer owner-session");
|
||||
return new Response(JSON.stringify({ code: "forbidden", message: "owner mismatch secret://internal" }), {
|
||||
status: 403,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
const client = new PlatformApiClient("/api/v1", () => "owner-session");
|
||||
await expect(client.getServerInstance("other-server")).rejects.toMatchObject({
|
||||
status: 403,
|
||||
code: "forbidden",
|
||||
message: "没有权限访问该资源。"
|
||||
});
|
||||
expect(onAuthFailure).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
function jsonResponse(body: unknown): Response {
|
||||
|
||||
@@ -21,6 +21,7 @@ import type {
|
||||
ComponentKeyResponse,
|
||||
ComponentKeyResetRequest,
|
||||
CurrentUserResponse,
|
||||
DependencyCatalogResponse,
|
||||
DependencyJobRequest,
|
||||
FileOperationDispatchRequest,
|
||||
FileOperationDispatchResponse,
|
||||
@@ -50,7 +51,10 @@ import type {
|
||||
RunDistributionResponse,
|
||||
RunEndpointListResponse,
|
||||
RunUpdateJobResponse,
|
||||
RunUpdateJobListResponse,
|
||||
RunUpdateRequest,
|
||||
RuntimeBindingResponse,
|
||||
RuntimeBindingUpdateRequest,
|
||||
ServerConfigResponse,
|
||||
ServerConfigDiffPreviewRequest,
|
||||
ServerConfigDiffPreviewResponse,
|
||||
@@ -65,6 +69,12 @@ import type {
|
||||
ServerMemberListResponse,
|
||||
ServerMemberRequest,
|
||||
ServerMetricsListResponse,
|
||||
MetricSampleListResponse,
|
||||
BackupListResponse,
|
||||
BackupResponse,
|
||||
RemoteAdapterDeclarationListResponse,
|
||||
RemoteAdapterRequest,
|
||||
RemoteAdapterResponse,
|
||||
ServerRuntimeActionsResponse,
|
||||
UserCreateRequest,
|
||||
UserListResponse,
|
||||
@@ -75,13 +85,30 @@ import type {
|
||||
UserUpdateRequest
|
||||
} from "./types";
|
||||
import { readWebRuntimeEnv } from "../schemas/env";
|
||||
import { parseSafeDependencyCatalog, parseSafeRunUpdate, parseSafeRunUpdateList } from "../schemas/runtimeUpdates";
|
||||
|
||||
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) {}
|
||||
|
||||
@@ -119,6 +146,14 @@ export class PlatformApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
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",
|
||||
@@ -126,12 +161,19 @@ export class PlatformApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
async stopServerInstance(id: string, request: ServerLifecycleCommandRequest): Promise<ServerLifecycleResponse> {
|
||||
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`);
|
||||
@@ -177,10 +219,9 @@ export class PlatformApiClient {
|
||||
if (sessionToken) {
|
||||
headers.set("Authorization", `Bearer ${sessionToken}`);
|
||||
}
|
||||
const response = await fetch(`${this.baseUrl}/artifacts/${encodeURIComponent(id)}/content?${params.toString()}`, { headers });
|
||||
const response = await fetch(`${this.baseUrl}/artifacts/${encodeURIComponent(id)}/content?${params.toString()}`, { headers, credentials: "same-origin" });
|
||||
if (!response.ok) {
|
||||
const apiError = await safeReadError(response);
|
||||
throw new Error(apiError?.message ?? `request failed: ${response.status}`);
|
||||
throw await responseError(response);
|
||||
}
|
||||
const payload = await response.arrayBuffer();
|
||||
return {
|
||||
@@ -225,10 +266,15 @@ export class PlatformApiClient {
|
||||
}
|
||||
|
||||
async pushRunUpdate(id: string, request: RunUpdateRequest): Promise<RunUpdateJobResponse> {
|
||||
return this.request<RunUpdateJobResponse>(`/server-instances/${encodeURIComponent(id)}/run/update`, {
|
||||
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> {
|
||||
@@ -259,6 +305,10 @@ export class PlatformApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
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",
|
||||
@@ -340,6 +390,27 @@ export class PlatformApiClient {
|
||||
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 getServerConfig(id: string): Promise<ServerConfigResponse> {
|
||||
return this.request<ServerConfigResponse>(`/server-instances/${encodeURIComponent(id)}/config`);
|
||||
}
|
||||
@@ -446,14 +517,14 @@ export class PlatformApiClient {
|
||||
|
||||
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) {
|
||||
const apiError = await safeReadError(response);
|
||||
throw new Error(apiError?.message ?? `request failed: ${response.status}`);
|
||||
throw await responseError(response);
|
||||
}
|
||||
|
||||
if (options.parseJson === false || response.status === 204) {
|
||||
@@ -480,6 +551,21 @@ async function safeReadError(response: Response): Promise<ApiErrorResponse | nul
|
||||
}
|
||||
}
|
||||
|
||||
async function responseError(response: Response): Promise<PlatformApiError> {
|
||||
const apiError = await safeReadError(response);
|
||||
const message = response.status === 401
|
||||
? "会话已失效,请重新登录。"
|
||||
: response.status === 403
|
||||
? "没有权限访问该资源。"
|
||||
: 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 marketplaceQuery(filter: MarketplacePluginFilterRequest): string {
|
||||
const params = new URLSearchParams();
|
||||
if (filter.status && filter.status !== "all") {
|
||||
|
||||
@@ -16,9 +16,14 @@ API clients and DTO types live here, not inside page components.
|
||||
|
||||
Every API client must use named request and response types.
|
||||
|
||||
`PlatformApiClient` converts 401 into a safe re-login error, clears its in-memory bearer token, and notifies the session store to remove browser persistence. A 403 remains a safe capability/ownership denial and does not disclose server error details. Neither error path renders tokens, secret refs, paths, or sockets.
|
||||
|
||||
Normal browser login uses the platform's HttpOnly SameSite cookie and `credentials=same-origin`; the JSON response does not expose a session token. Reading an older localStorage bearer remains a migration compatibility path only, and any 401 removes it.
|
||||
|
||||
## Server Management Workflows
|
||||
|
||||
- `createServerWorkflow` posts `ServerLifecycleCreateRequest` to `/server-instances/workflows/create` and receives the accepted instance plus install job.
|
||||
- `createServerWorkflow` posts `ServerLifecycleCreateRequest` with a declared `profileKey` and initial logical `bindings` to `/server-instances/workflows/create`, and receives the accepted instance plus install job only after binding validation.
|
||||
- `getServerRuntimeBinding` reads `/server-instances/{id}/runtime-binding`; `updateServerRuntimeBinding` patches the selected profile and logical refs. Responses contain only profile metadata, logical key names, configured/secret-backed flags, missing keys, and safe reasons. They never contain stored refs or secret values.
|
||||
- `startServerInstance` and `stopServerInstance` post `ServerLifecycleCommandRequest` with the current config version and receive the lifecycle job response.
|
||||
- `listServerAdministratorCandidates`, `addServerAdministrator`, and `removeServerAdministrator` call server membership endpoints so server owners can invite or remove active non-platform-admin server administrators.
|
||||
- `getServerConfig`, `previewServerConfigDiff`, and `approveServerConfigWrite` call platform-mediated config routes. ServerDetailPage must preview the platform diff first, keep the explicit confirmation step, and dispatch writes only through the approval API.
|
||||
@@ -27,15 +32,21 @@ Every API client must use named request and response types.
|
||||
- `authorizePluginBridge` posts `PluginBridgeAuthorizeRequest` to `/plugin-bridge/authorize` for preflight decisions.
|
||||
- `executePluginBridge` posts `PluginBridgeExecuteRequest` to `/plugin-bridge/execute` from host-owned bridge dispatch utilities only. Plugin pages receive typed `PluginBridgeExecuteResponse` envelopes and never receive the platform API client, bearer token, raw provider key, run socket, host path, or storage credential.
|
||||
- `invokeAI` posts `AIInvocationRequest` to `/ai/invocations` for platform-mediated AI assistance. Responses carry redacted recommendations, usage metadata, optional reviewable config suggestions, and safe errors; they must not include provider base URLs, key refs, raw keys, or direct provider transport details.
|
||||
- `listRunEndpoints` and `listJobs` provide refresh data for endpoint availability, capacity, and pending lifecycle status.
|
||||
- `listRunEndpoints` and `listJobs` provide refresh data for endpoint availability, capacity, and durable lifecycle status. Job projections include `retrying`, attempt/max-attempt counts, next retry timing, safe ack/lease deadlines, cancellation timestamps/reason, terminal time, and reconciliation outcome/count.
|
||||
- `getDependencyCatalog` reads `GET /server-instances/{id}/dependencies` and returns only target-matched probe state/evidence, typed plan step summaries, approved download hosts, and immutable SHA-256 `planDigest` values. Install requests must submit the selected digest; the browser never receives bindings, commands, paths, credentials, tokens, or private download refs.
|
||||
- `listRunUpdates` reads `GET /server-instances/{id}/run/update` and returns only target, artifact checksum, release identity, phase, bounded audit message, rollback flag, and timestamps. The UI treats `restart-requested`/`activating` as non-terminal until a later safe projection confirms health.
|
||||
- `listMetricHistory`, `listBackups`, and `getBackup` read bounded owner-scoped metric and backup projections. Backup responses contain artifact IDs/checksums and recovery/retention state only; they never include body bytes or storage paths.
|
||||
- `listRemoteAdapters` and `requestRemoteAdapter` use declaration-backed logical target keys and return queued status/result references. The browser never receives adapter credentials, host addresses, sockets, Run tokens, leases, session hashes, or secret refs.
|
||||
- Server management DTOs may include bounded `ownerUserId` and `adminUserIds` metadata, but must not include raw run credentials, host paths, direct socket details, user password hashes, or AI provider keys.
|
||||
- Server creation and detail forms derive profile choices and binding fields from `GamePluginResponse.runtimeProfiles`; they must not hardcode a complete state or game-specific machine paths.
|
||||
- AI provider responses expose `apiKeyConfigured` only. Existing secret refs are never rehydrated into edit forms; a blank update preserves the platform-owned secret reference.
|
||||
|
||||
## Redesign Contract Gaps (redesign-platform-web-interactions)
|
||||
|
||||
Existing platform APIs already cover server lifecycle, jobs, log stream metadata and cursor query, audit events, users, run endpoints, game plugins, plugin bridge authorization, and AI provider health/test. The redesigned UI additionally declares the following frontend contracts; where the platform backend does not yet serve them, the UI must degrade to a clearly labeled local/unavailable state instead of failing silently:
|
||||
|
||||
- `POST /api/v1/auth/register` (`RegisterRequest`/`AuthSessionResponse`): visitor registration. Implemented: the first registered user becomes an active platform administrator; later self-registered users become pending server-scoped users and do not receive platform administrator privileges.
|
||||
- `POST /api/v1/auth/login` (`LoginRequest`/`AuthSessionResponse`) and `POST /api/v1/auth/logout`: implemented bearer session lifecycle for authenticated workspace entry.
|
||||
- `POST /api/v1/auth/login` (`LoginRequest`/`AuthSessionResponse`), `POST /api/v1/auth/rotate`, and `POST /api/v1/auth/logout`: implemented bounded, durable bearer session lifecycle for authenticated workspace entry.
|
||||
- `GET /api/v1/users/current` (`CurrentUserResponse`): implemented current session identity, roles, profile summary, and theme preference reference for role-aware navigation and default landing.
|
||||
- `PUT /api/v1/users/current/profile` (`UserProfileUpdateRequest`/`CurrentUserResponse`): implemented current-user profile updates such as display name, avatar reference, phone, QQ, and bounded contact fields.
|
||||
- `PUT /api/v1/users/current/theme` (`UserThemePreferenceRequest`/`UserThemePreferenceResponse`): implemented per-user theme preferences, including selected palette IDs such as `mecha-black` or `magical-girl`, uploaded background reference or safe persisted data URL metadata, and readable overlay preference.
|
||||
@@ -47,4 +58,6 @@ Existing platform APIs already cover server lifecycle, jobs, log stream metadata
|
||||
- `POST /api/v1/ai/config-suggestions` (`LlmConfigSuggestionRequest`/`LlmConfigSuggestionResponse`) and `POST /api/v1/ai/invocations` (`AIInvocationRequest`/`AIInvocationResponse`): platform-mediated AI recommendation or diff scoped to one server. Provider keys stay in `platform/`; responses carry only recommendation text, usage metadata, and reviewable suggestions, never keys or provider secrets.
|
||||
- Per-server plugin controls are rendered from installed plugin manifests (`bridgeActions`, `lifecycleActions`, `pages`, `declaredPermissions`); a richer declared-control schema remains a future plugin contract. Hosted bridge execution uses `POST /api/v1/plugin-bridge/execute` for server context, scoped file, log, job, artifact reference, and AI action envelopes instead of direct plugin fetches to platform internals.
|
||||
- Operation/job traceability reuses `GET /api/v1/jobs`, `GET /api/v1/jobs/{id}`, `POST /api/v1/jobs/{id}/cancel`, and `GET /api/v1/audit-events`; the frontend wraps these in one visible operation lifecycle per user intent.
|
||||
|
||||
Browser Job contracts explicitly exclude raw or hashed lease tokens, Run session tokens/generations, secret refs, host paths, sockets, and credentials. The safe schema rejects those keys, and existing API client 401/403 behavior remains authoritative for expired sessions and cross-owner access.
|
||||
- Log filtering by level/keyword/time/source is applied client-side over `POST /api/v1/log-streams/query` (`LogStreamCursorRequest`) results until the platform exposes server-side filters.
|
||||
|
||||
+276
-12
@@ -8,8 +8,10 @@ export interface HealthResponse {
|
||||
export type GamePluginStatus = "installed" | "disabled" | "invalid" | "updating";
|
||||
export type ServerInstanceState = "draft" | "installing" | "ready" | "running" | "stopped" | "failed" | "deleted";
|
||||
export type RunEndpointStatus = "online" | "offline" | "degraded" | "disabled";
|
||||
export type JobState = "queued" | "accepted" | "running" | "succeeded" | "failed" | "cancelled";
|
||||
export type ServerLifecycleAction = "create" | "start" | "stop";
|
||||
export type JobState = "queued" | "accepted" | "running" | "retrying" | "succeeded" | "failed" | "cancelled";
|
||||
export type DependencyState = "unknown" | "present" | "missing" | "installing" | "failed";
|
||||
export type RunUpdatePhase = "queued" | "downloading" | "staged" | "restart-requested" | "activating" | "succeeded" | "rolled-back" | "failed";
|
||||
export type ServerLifecycleAction = "create" | "start" | "stop" | "status";
|
||||
|
||||
export interface PluginPermissionsResponse {
|
||||
ai: boolean;
|
||||
@@ -17,6 +19,7 @@ export interface PluginPermissionsResponse {
|
||||
files: boolean;
|
||||
jobs: boolean;
|
||||
artifacts: boolean;
|
||||
remoteAccess?: boolean;
|
||||
}
|
||||
|
||||
export interface GamePluginPageResponse {
|
||||
@@ -27,6 +30,77 @@ export interface GamePluginPageResponse {
|
||||
bridgeActions?: string[];
|
||||
}
|
||||
|
||||
export interface RuntimeDiscoveryProbeResponse {
|
||||
key: string;
|
||||
kind: string;
|
||||
targetKey: string;
|
||||
required?: boolean;
|
||||
expected?: string;
|
||||
platforms?: string[];
|
||||
}
|
||||
|
||||
export interface RuntimeLifecycleProfileResponse {
|
||||
key: string;
|
||||
mode: "local-process" | "hosted-ftp-rcon" | "ftp-only" | "custom-client";
|
||||
capabilities: string[];
|
||||
actionRefs?: Record<string, string>;
|
||||
transportKeys?: string[];
|
||||
clientManagerRef?: string;
|
||||
platforms?: string[];
|
||||
}
|
||||
|
||||
export interface RuntimeDependencyProbeResponse {
|
||||
key: string;
|
||||
kind: string;
|
||||
targetKey: string;
|
||||
required?: boolean;
|
||||
minimumVersion?: string;
|
||||
platforms?: string[];
|
||||
}
|
||||
|
||||
export interface RuntimeInstallPlanResponse {
|
||||
key: string;
|
||||
title: string;
|
||||
platforms?: string[];
|
||||
steps: Array<{ type: string; targetKey: string; packageManager?: string; packageName?: string; version?: string; downloadRef?: string; checksum?: string }>;
|
||||
}
|
||||
|
||||
export interface RuntimeLogSourceResponse {
|
||||
key: string;
|
||||
kind: string;
|
||||
targetKey?: string;
|
||||
streamKey: string;
|
||||
cursorKind?: string;
|
||||
retentionDays?: number;
|
||||
}
|
||||
|
||||
export interface RuntimeTransportProfileResponse {
|
||||
key: string;
|
||||
kind: string;
|
||||
targetKey?: string;
|
||||
capabilities: string[];
|
||||
}
|
||||
|
||||
export interface RuntimeClientManagerProfileResponse {
|
||||
key: string;
|
||||
displayName?: string;
|
||||
repository: { url: string; revisionPolicy: string; branch?: string; tag?: string; revision?: string };
|
||||
supportedTargets: Array<{ os: string; arch: string }>;
|
||||
build: { system: string; workspaceRef?: string; entryRef?: string };
|
||||
configTemplates?: Array<{ key: string; templateRef: string; outputRef: string }>;
|
||||
outputArtifacts: string[];
|
||||
}
|
||||
|
||||
export interface GamePluginRuntimeProfilesResponse {
|
||||
discovery?: RuntimeDiscoveryProbeResponse[];
|
||||
lifecycleProfiles?: RuntimeLifecycleProfileResponse[];
|
||||
dependencyProbes?: RuntimeDependencyProbeResponse[];
|
||||
installPlans?: RuntimeInstallPlanResponse[];
|
||||
logSources?: RuntimeLogSourceResponse[];
|
||||
transportProfiles?: RuntimeTransportProfileResponse[];
|
||||
clientManagers?: RuntimeClientManagerProfileResponse[];
|
||||
}
|
||||
|
||||
export interface GamePluginResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -46,6 +120,7 @@ export interface GamePluginResponse {
|
||||
tags: string[];
|
||||
aiPurposes: string[];
|
||||
validationViolations?: string[];
|
||||
runtimeProfiles?: GamePluginRuntimeProfilesResponse;
|
||||
status: GamePluginStatus;
|
||||
}
|
||||
|
||||
@@ -104,7 +179,10 @@ export interface ServerInstanceResponse {
|
||||
ownerUserId?: string;
|
||||
adminUserIds: string[];
|
||||
state: ServerInstanceState;
|
||||
configVersion: number;
|
||||
configVersion: number;
|
||||
configKey?: string;
|
||||
configChecksum?: string;
|
||||
configUpdatedAt?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -124,10 +202,39 @@ export interface ServerLifecycleCreateRequest {
|
||||
runEndpointId: string;
|
||||
name: string;
|
||||
idempotencyKey: string;
|
||||
profileKey: string;
|
||||
bindings: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface RuntimeBindingUpdateRequest {
|
||||
profileKey: string;
|
||||
bindings: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface RuntimeBindingKeyResponse {
|
||||
key: string;
|
||||
required: boolean;
|
||||
configured: boolean;
|
||||
secret: boolean;
|
||||
}
|
||||
|
||||
export interface RuntimeBindingResponse {
|
||||
serverInstanceId: string;
|
||||
pluginId: string;
|
||||
profileKey?: string;
|
||||
mode?: string;
|
||||
configured: boolean;
|
||||
keys: RuntimeBindingKeyResponse[];
|
||||
missingKeys: string[];
|
||||
status: "complete" | "incomplete";
|
||||
reason?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface ServerLifecycleCommandRequest {
|
||||
expectedConfigVersion: number;
|
||||
expectedConfigVersion: number;
|
||||
expectedChecksum?: string;
|
||||
idempotencyKey: string;
|
||||
}
|
||||
|
||||
@@ -149,6 +256,8 @@ export interface RunEndpointResponse {
|
||||
id: string;
|
||||
displayName: string;
|
||||
version: string;
|
||||
platform?: string;
|
||||
architecture?: string;
|
||||
status: RunEndpointStatus;
|
||||
capabilities: string[];
|
||||
capacity: RunCapacityResponse;
|
||||
@@ -175,11 +284,39 @@ export interface JobResponse {
|
||||
idempotencyKey: string;
|
||||
state: JobState;
|
||||
progress: JobProgressBody;
|
||||
resultRef?: string;
|
||||
resultRef?: string;
|
||||
executionResult?: JobExecutionResultResponse;
|
||||
retryPolicy: {
|
||||
maxAttempts: number;
|
||||
initialBackoffSeconds: number;
|
||||
maxBackoffSeconds: number;
|
||||
};
|
||||
attempt: number;
|
||||
nextAttemptAt?: string;
|
||||
ackDeadlineAt?: string;
|
||||
leaseExpiresAt?: string;
|
||||
cancelReason?: string;
|
||||
cancelRequestedAt?: string;
|
||||
cancelCompletedAt?: string;
|
||||
terminalAt?: string;
|
||||
lastReconciledAt?: string;
|
||||
reconcileCount: number;
|
||||
reconcileOutcome?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface JobExecutionResultResponse {
|
||||
kind?: string;
|
||||
processState?: string;
|
||||
exitClassification?: string;
|
||||
exitCode?: number;
|
||||
version?: number;
|
||||
checksum?: string;
|
||||
sizeBytes?: number;
|
||||
auditSummary?: string;
|
||||
}
|
||||
|
||||
export interface JobListResponse {
|
||||
items: JobResponse[];
|
||||
count: number;
|
||||
@@ -288,13 +425,25 @@ export interface RunUpdateJobResponse {
|
||||
runEndpointId: string;
|
||||
artifactId: string;
|
||||
checksum: string;
|
||||
targetOs: string;
|
||||
targetArch: string;
|
||||
targetRelease?: string;
|
||||
previousVersion?: string;
|
||||
jobId?: string;
|
||||
idempotencyKey?: string;
|
||||
status: string;
|
||||
phase: RunUpdatePhase;
|
||||
message?: string;
|
||||
rollback: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface RunUpdateJobListResponse {
|
||||
items: RunUpdateJobResponse[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface ClientManagerBuildRequest {
|
||||
profileKey: string;
|
||||
targetOs: string;
|
||||
@@ -351,9 +500,51 @@ export interface DependencyJobRequest {
|
||||
installPlanKey?: string;
|
||||
targetOs?: string;
|
||||
targetArch?: string;
|
||||
planDigest?: string;
|
||||
idempotencyKey?: string;
|
||||
}
|
||||
|
||||
export interface DependencyProbeViewResponse {
|
||||
key: string;
|
||||
kind: string;
|
||||
required: boolean;
|
||||
minimumVersion?: string;
|
||||
state: DependencyState;
|
||||
evidence?: string;
|
||||
installPlanKey?: string;
|
||||
}
|
||||
|
||||
export interface DependencyPlanStepViewResponse {
|
||||
type: string;
|
||||
targetKey: string;
|
||||
packageManager?: string;
|
||||
packageName?: string;
|
||||
version?: string;
|
||||
downloadHost?: string;
|
||||
sizeBytes?: number;
|
||||
}
|
||||
|
||||
export interface DependencyPlanViewResponse {
|
||||
key: string;
|
||||
title: string;
|
||||
targetOs: string;
|
||||
targetArch: string;
|
||||
digest: string;
|
||||
steps: DependencyPlanStepViewResponse[];
|
||||
}
|
||||
|
||||
export interface DependencyCatalogResponse {
|
||||
serverInstanceId: string;
|
||||
pluginId: string;
|
||||
pluginVersion: string;
|
||||
profileKey: string;
|
||||
targetOs: string;
|
||||
targetArch: string;
|
||||
probes: DependencyProbeViewResponse[];
|
||||
plans: DependencyPlanViewResponse[];
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface LogBackfillRequest {
|
||||
sourceKey: string;
|
||||
checkpointRef?: string;
|
||||
@@ -435,7 +626,7 @@ export interface AiProviderResponse {
|
||||
name: string;
|
||||
kind: AiProviderKind;
|
||||
baseUrl: string;
|
||||
apiKeyRef: string;
|
||||
apiKeyConfigured: boolean;
|
||||
models: string[];
|
||||
defaultModel?: string;
|
||||
relayMode: AiRelayMode;
|
||||
@@ -555,6 +746,7 @@ export interface AuthSessionResponse {
|
||||
sessionId?: string;
|
||||
status: "authenticated" | "pending";
|
||||
message?: string;
|
||||
expiresAt?: string;
|
||||
}
|
||||
|
||||
export interface UserProfileUpdateRequest {
|
||||
@@ -608,12 +800,79 @@ export interface ServerMetricsListResponse {
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface MetricSampleResponse extends ServerMetricsResponse {
|
||||
id?: string;
|
||||
runEndpointId?: string;
|
||||
}
|
||||
|
||||
export interface MetricSampleListResponse {
|
||||
items: MetricSampleResponse[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
export type BackupState = "pending" | "available" | "failed" | "expired";
|
||||
|
||||
export interface BackupResponse {
|
||||
id: string;
|
||||
serverInstanceId: string;
|
||||
artifactId: string;
|
||||
checksum: string;
|
||||
sizeBytes: number;
|
||||
state: BackupState;
|
||||
recoveryStatus?: string;
|
||||
retentionUntil?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface BackupListResponse {
|
||||
items: BackupResponse[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface RemoteAdapterDeclarationResponse {
|
||||
key: string;
|
||||
kind: string;
|
||||
targetKeys: string[];
|
||||
capabilities: string[];
|
||||
timeoutSeconds: number;
|
||||
maxAttempts: number;
|
||||
}
|
||||
|
||||
export interface RemoteAdapterDeclarationListResponse {
|
||||
items: RemoteAdapterDeclarationResponse[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface RemoteAdapterRequest {
|
||||
declarationKey: string;
|
||||
targetKey: string;
|
||||
capability: string;
|
||||
timeoutSeconds?: number;
|
||||
maxAttempts?: number;
|
||||
idempotencyKey: string;
|
||||
}
|
||||
|
||||
export interface RemoteAdapterResponse {
|
||||
requestId: string;
|
||||
serverInstanceId: string;
|
||||
declarationKey: string;
|
||||
targetKey: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
retryable: boolean;
|
||||
message: string;
|
||||
resultRef?: string;
|
||||
completedAt?: string;
|
||||
}
|
||||
|
||||
export interface ServerConfigResponse {
|
||||
serverInstanceId: string;
|
||||
configVersion: number;
|
||||
format: string;
|
||||
key?: string;
|
||||
content: string;
|
||||
content: string;
|
||||
checksum?: string;
|
||||
source?: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -628,7 +887,8 @@ export interface ConfigDiffLineResponse {
|
||||
}
|
||||
|
||||
export interface ServerConfigDiffPreviewRequest {
|
||||
expectedConfigVersion: number;
|
||||
expectedConfigVersion: number;
|
||||
expectedChecksum?: string;
|
||||
key: string;
|
||||
proposedContent?: string;
|
||||
proposedContentInputRef?: string;
|
||||
@@ -636,7 +896,8 @@ export interface ServerConfigDiffPreviewRequest {
|
||||
|
||||
export interface ServerConfigDiffPreviewResponse {
|
||||
serverInstanceId: string;
|
||||
configVersion: number;
|
||||
configVersion: number;
|
||||
checksum?: string;
|
||||
key: string;
|
||||
currentContent: string;
|
||||
proposedContent?: string;
|
||||
@@ -648,7 +909,8 @@ export interface ServerConfigDiffPreviewResponse {
|
||||
}
|
||||
|
||||
export interface ServerConfigWriteApprovalRequest {
|
||||
expectedConfigVersion: number;
|
||||
expectedConfigVersion: number;
|
||||
expectedChecksum?: string;
|
||||
key: string;
|
||||
proposedContent?: string;
|
||||
proposedContentInputRef?: string;
|
||||
@@ -668,8 +930,10 @@ export interface FileOperationDispatchRequest {
|
||||
pluginId?: string;
|
||||
operation: FileOperationKind;
|
||||
key: string;
|
||||
inputRef?: string;
|
||||
expectedConfigVersion?: number;
|
||||
inputRef?: string;
|
||||
content?: string;
|
||||
expectedConfigVersion?: number;
|
||||
expectedChecksum?: string;
|
||||
idempotencyKey: string;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user