export type PluginPermission = | "server.create" | "server.read" | "server.lifecycle" | "server.files.read" | "server.files.write" | "server.logs.read" | "server.artifacts.read" | "server.artifacts.write" | "ai.invoke"; export type RunCapability = | "process.install" | "process.start" | "process.stop" | "process.restart" | "process.status" | "files.list" | "files.read" | "files.write" | "files.patch" | "logs.read" | "artifacts.read" | "artifacts.write" | "ai.invoke"; export type AIPurpose = "config.read" | "config.generate" | "config.suggest" | "logs.diagnose" | "files.suggest"; export type PluginBridgeAction = | "server.instances.read" | "jobs.dispatch" | "logs.query" | "artifacts.open" | "files.request" | "ai.invoke"; export type PluginBridgeRequestPayload = Record; export type PluginBridgeRequest = { id: string; pluginId: string; routeKey: string; serverInstanceId?: string; action: PluginBridgeAction; payload: TPayload; }; export type PluginBridgeResponse = | { id: string; ok: true; result: TResult; } | { id: string; ok: false; error: PluginBridgeError; }; export interface PluginBridgeError { code: "unsupported_action" | "missing_permission" | "invalid_payload" | "denied" | "permission_denied" | "unsafe_payload" | "platform_error" | "deferred"; message: string; details?: string[]; } export interface PluginBridgeExecutionRequest = Record> { requestId: string; pluginId: string; routeKey: string; serverInstanceId?: string; action: PluginBridgeAction; aiPurpose?: AIPurpose; payload?: TPayload; } export interface PluginAIInvocationRequest { requestId: string; pluginId: string; routeKey: string; serverInstanceId?: string; purpose: AIPurpose; prompt: string; currentConfig?: string; contextRefs?: Record; } export interface PluginAIInvocationResponse { requestId: string; purpose: AIPurpose; status: "ok" | "denied" | "error" | string; recommendation?: string; suggestedConfig?: string; usage?: { model?: string; mocked?: boolean; inputTokens?: number; outputTokens?: number }; error?: PluginBridgeError; } export type PluginArtifactOpenPayload = Record & { artifactId: string; }; export type PluginLifecycleDispatchPayload = Record & { lifecycleAction: "start" | "stop"; capability: "process.start" | "process.stop"; expectedConfigVersion: string; idempotencyKey: string; }; export interface PluginArtifactReference { artifactId: string; filename: string; contentType: string; sizeBytes: number; checksum: string; downloadUrl: string; expiresAt: string; rangeSupported: boolean; chunkSizeBytes: number; storageBehavior?: string; } export type PluginBridgeExecutionResponse = Record> = { requestId: string; pluginId: string; routeKey: string; serverInstanceId?: string; action: PluginBridgeAction; status: "ok" | "queued" | "denied" | "unsupported" | "cancelled" | "error" | string; result?: TResult; error?: PluginBridgeError; }; export type PluginBridgeActionPolicy = { permissions: PluginPermission[]; aiPurposeRequired?: boolean; }; export const pluginBridgeActionPolicies: Record = { "server.instances.read": { permissions: ["server.read"] }, "jobs.dispatch": { permissions: ["server.lifecycle"] }, "logs.query": { permissions: ["server.logs.read"] }, "artifacts.open": { permissions: ["server.artifacts.read"] }, "files.request": { permissions: ["server.files.read"] }, "ai.invoke": { permissions: ["ai.invoke"], aiPurposeRequired: true } }; export type PluginLifecycleAction = "install" | "start" | "stop" | "restart" | "status"; export type GamePluginActions = Partial> & { install: string; start: string; stop: string; }; export interface GamePluginPage { key: string; title: string; path: string; permissions?: PluginPermission[]; bridgeActions?: PluginBridgeAction[]; } export interface GamePluginBridge { actions: PluginBridgeAction[]; } export interface GamePluginManifest { id: `game.${string}`; name: string; description?: string; version: string; kind: "game-plugin"; tags?: string[]; server: { type: string; displayName: string; supportedOS?: Array<"windows" | "linux" | "darwin">; createFormSchema: string; }; bridge?: GamePluginBridge; capabilities: RunCapability[]; permissions: PluginPermission[]; actions?: GamePluginActions; pages?: GamePluginPage[]; ai?: { purposes?: AIPurpose[]; }; } export interface PluginBridgeContext { pluginId: string; routeKey: string; serverInstanceId?: string; permissions: PluginPermission[]; aiPurposes?: AIPurpose[]; } export function hasPluginPermission(context: PluginBridgeContext, permission: PluginPermission): boolean { return context.permissions.includes(permission); } export function canRequestBridgeAction( context: PluginBridgeContext, action: PluginBridgeAction, options: { aiPurpose?: AIPurpose } = {} ): boolean { const policy = pluginBridgeActionPolicies[action]; if (!policy.permissions.every((permission) => hasPluginPermission(context, permission))) { return false; } if (policy.aiPurposeRequired) { return typeof options.aiPurpose === "string" && (context.aiPurposes ?? []).includes(options.aiPurpose); } return true; } export function createBridgeRequest(input: { id: string; context: PluginBridgeContext; action: PluginBridgeAction; payload: TPayload; }): PluginBridgeRequest { return { id: input.id, pluginId: input.context.pluginId, routeKey: input.context.routeKey, serverInstanceId: input.context.serverInstanceId, action: input.action, payload: input.payload }; } export function createBridgeExecutionRequest>(input: { requestId: string; context: PluginBridgeContext; action: PluginBridgeAction; aiPurpose?: AIPurpose; payload?: TPayload; }): PluginBridgeExecutionRequest { return { requestId: input.requestId, pluginId: input.context.pluginId, routeKey: input.context.routeKey, serverInstanceId: input.context.serverInstanceId, action: input.action, aiPurpose: input.aiPurpose, payload: input.payload }; } export function createArtifactOpenRequest(input: { requestId: string; context: PluginBridgeContext; artifactId: string; }): PluginBridgeExecutionRequest { return createBridgeExecutionRequest({ requestId: input.requestId, context: input.context, action: "artifacts.open", payload: { artifactId: input.artifactId } }); } export function createLifecycleDispatchRequest(input: { requestId: string; context: PluginBridgeContext; action: "start" | "stop"; expectedConfigVersion: number; idempotencyKey: string; }): PluginBridgeExecutionRequest { return createBridgeExecutionRequest({ requestId: input.requestId, context: input.context, action: "jobs.dispatch", payload: { lifecycleAction: input.action, capability: input.action === "start" ? "process.start" : "process.stop", expectedConfigVersion: String(input.expectedConfigVersion), idempotencyKey: input.idempotencyKey } }); } export function parseArtifactReference(result: Record | undefined): PluginArtifactReference | undefined { if (!result) { return undefined; } const sizeBytes = Number(result.sizeBytes); const chunkSizeBytes = Number(result.chunkSizeBytes); const reference: PluginArtifactReference = { artifactId: result.artifactId ?? "", filename: result.filename ?? "artifact.bin", contentType: result.contentType ?? "application/octet-stream", sizeBytes, checksum: result.checksum ?? "", downloadUrl: result.downloadUrl ?? "", expiresAt: result.expiresAt ?? "", rangeSupported: result.rangeSupported === "true", chunkSizeBytes, storageBehavior: result.storageBehavior }; if (!reference.artifactId || !Number.isFinite(sizeBytes) || sizeBytes <= 0 || !Number.isFinite(chunkSizeBytes) || chunkSizeBytes <= 0) { return undefined; } if (!reference.downloadUrl.startsWith(`/api/v1/artifacts/${encodeURIComponent(reference.artifactId)}/content`)) { return undefined; } for (const value of Object.values(reference)) { if (typeof value === "string" && containsUnsafeReferenceContent(value)) { return undefined; } } return reference; } export function parseBridgeExecutionResponse>(response: PluginBridgeExecutionResponse): PluginBridgeExecutionResponse { const safeError = response.error ? bridgeError(response.error.code, response.error.message, response.error.details ?? []) : undefined; return { requestId: response.requestId, pluginId: response.pluginId, routeKey: response.routeKey, serverInstanceId: response.serverInstanceId, action: response.action, status: response.status, result: response.result ? { ...response.result } : undefined, error: safeError }; } export function createAIInvocationRequest(input: { requestId: string; context: PluginBridgeContext; purpose: AIPurpose; prompt: string; currentConfig?: string; contextRefs?: Record; }): PluginAIInvocationRequest { return { requestId: input.requestId, pluginId: input.context.pluginId, routeKey: input.context.routeKey, serverInstanceId: input.context.serverInstanceId, purpose: input.purpose, prompt: input.prompt, currentConfig: input.currentConfig, contextRefs: input.contextRefs }; } export function parseAIInvocationResponse(response: PluginAIInvocationResponse): PluginAIInvocationResponse { return { requestId: response.requestId, purpose: response.purpose, status: response.status, recommendation: response.recommendation, suggestedConfig: response.suggestedConfig, usage: response.usage ? { ...response.usage } : undefined, error: response.error ? bridgeError(response.error.code, response.error.message, response.error.details ?? []) : undefined }; } export function bridgeError( code: PluginBridgeError["code"], message: string, details: string[] = [] ): PluginBridgeError { return { code, message, details }; } function containsUnsafeReferenceContent(value: string): boolean { const lowered = value.trim().toLowerCase(); return ( lowered.includes("/users/") || lowered.includes("/private/") || lowered.includes("unix://") || lowered.includes("tcp://") || lowered.includes("bearer ") || lowered.includes("password=") || lowered.includes("api_key=") || lowered.includes("apikey=") || lowered.includes("storage://") || lowered.includes("file://") || lowered.startsWith("sk-") ); }