1306 lines
40 KiB
TypeScript
1306 lines
40 KiB
TypeScript
export type PluginPermission =
|
|
| "server.create"
|
|
| "server.read"
|
|
| "server.lifecycle"
|
|
| "server.files.read"
|
|
| "server.files.write"
|
|
| "server.logs.read"
|
|
| "server.artifacts.read"
|
|
| "server.artifacts.write"
|
|
| "server.remote.access"
|
|
| "server.run.distribution"
|
|
| "server.dependencies.manage"
|
|
| "server.client-manager.manage"
|
|
| "server.game-client.read"
|
|
| "server.game-client.command"
|
|
| "server.game-client.maintenance"
|
|
| "ai.invoke";
|
|
|
|
export type RunCapability =
|
|
| "process.install"
|
|
| "process.start"
|
|
| "process.stop"
|
|
| "process.restart"
|
|
| "process.status"
|
|
| "deployment.plan.v1"
|
|
| "config.write"
|
|
| "files.list"
|
|
| "files.read"
|
|
| "files.write"
|
|
| "files.patch"
|
|
| "logs.read"
|
|
| "run.self-update"
|
|
| "distribution.build"
|
|
| "dependencies.check"
|
|
| "dependencies.install"
|
|
| "logs.backfill"
|
|
| "remote.ftp.read"
|
|
| "remote.ftp.write"
|
|
| "remote.rsync.read"
|
|
| "remote.rsync.write"
|
|
| "remote.run.files.read"
|
|
| "remote.run.files.write"
|
|
| "remote.run.process.start"
|
|
| "remote.run.process.stop"
|
|
| "remote.run.db.mysql.query"
|
|
| "remote.run.db.sqlite.probe"
|
|
| "remote.run.db.sqlite.query"
|
|
| "remote.run.logs.transfer"
|
|
| "remote.run.rcon.command"
|
|
| "remote.run.protected.sql"
|
|
| "remote.run.protected.rcon"
|
|
| "remote.run.program.command"
|
|
| "client-manager.deploy"
|
|
| "client-manager.control"
|
|
| "client-manager.update"
|
|
| "client-manager.rollback"
|
|
| "client-manager.uninstall"
|
|
| "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"
|
|
| "remote.access.request"
|
|
| "run.distribution.request"
|
|
| "dependencies.request"
|
|
| "logs.backfill.request"
|
|
| "client-manager.request"
|
|
| "plugin-lifecycle.request"
|
|
| "ai.invoke";
|
|
|
|
export type PluginBridgeRequestPayload = Record<string, unknown>;
|
|
|
|
export type PluginBridgeRequest<TPayload extends PluginBridgeRequestPayload = PluginBridgeRequestPayload> = {
|
|
id: string;
|
|
pluginId: string;
|
|
routeKey: string;
|
|
serverInstanceId?: string;
|
|
action: PluginBridgeAction;
|
|
payload: TPayload;
|
|
};
|
|
|
|
export type PluginBridgeResponse<TResult = unknown> =
|
|
| {
|
|
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<TPayload extends Record<string, string> = Record<string, string>> {
|
|
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<string, string>;
|
|
}
|
|
|
|
export interface PluginAIInvocationResponse {
|
|
requestId: string;
|
|
purpose: AIPurpose;
|
|
status: "ok" | "denied" | "error" | string;
|
|
recommendation?: string;
|
|
configRecommendation?: { diffId: string; key: string; suggestedConfig: string; diffSummary: string; expiresAt: string };
|
|
usage?: { model?: string; mocked?: boolean; inputTokens?: number; outputTokens?: number };
|
|
error?: PluginBridgeError;
|
|
}
|
|
|
|
export type PluginArtifactOpenPayload = Record<string, string> & {
|
|
artifactId: string;
|
|
};
|
|
|
|
export type PluginLifecycleDispatchPayload = Record<string, string> & {
|
|
lifecycleAction: "start" | "stop";
|
|
capability: "process.start" | "process.stop";
|
|
expectedConfigVersion: string;
|
|
idempotencyKey: string;
|
|
};
|
|
|
|
export type ProductionPluginLifecycleOperation = "install" | "enable" | "disable" | "upgrade" | "rollback" | "retire" | "dependency-check";
|
|
|
|
export type PluginProductionLifecyclePayload = Record<string, string> & {
|
|
operation: ProductionPluginLifecycleOperation;
|
|
targetVersion: string;
|
|
idempotencyKey: string;
|
|
confirmed: "true" | "false";
|
|
};
|
|
|
|
export type PluginRemoteAccessPayload = Record<string, string> & {
|
|
capability: Extract<RunCapability, `remote.${string}`>;
|
|
targetKey?: string;
|
|
inputRef?: string;
|
|
idempotencyKey: string;
|
|
};
|
|
|
|
export type PluginRunDistributionPayload = Record<string, string> & {
|
|
operation: "generate" | "download" | "reset-key" | "update";
|
|
targetOS?: RuntimePlatform;
|
|
targetArch?: RuntimeArch;
|
|
artifactId?: string;
|
|
idempotencyKey: string;
|
|
};
|
|
|
|
export type PluginDependencyActionPayload = Record<string, string> & {
|
|
operation: "check" | "install";
|
|
probeKey?: string;
|
|
planKey?: string;
|
|
planDigest?: string;
|
|
idempotencyKey: string;
|
|
};
|
|
|
|
export type PluginLogBackfillPayload = Record<string, string> & {
|
|
sourceKey: string;
|
|
cursor?: string;
|
|
limit?: string;
|
|
idempotencyKey: string;
|
|
};
|
|
|
|
export type PluginClientManagerPayload = Record<string, string> & {
|
|
operation:
|
|
| "generate"
|
|
| "download"
|
|
| "reset-key"
|
|
| "status"
|
|
| "deploy"
|
|
| "start"
|
|
| "stop"
|
|
| "restart"
|
|
| "update"
|
|
| "rollback"
|
|
| "revoke-session"
|
|
| "retry"
|
|
| "uninstall";
|
|
profileKey: string;
|
|
targetOS?: RuntimePlatform;
|
|
targetArch?: RuntimeArch;
|
|
artifactId?: string;
|
|
installationId?: string;
|
|
expectedDeploymentGeneration?: string;
|
|
idempotencyKey: string;
|
|
};
|
|
|
|
export type RemoteAccessMethod = "ftp" | "rsync" | "run";
|
|
export type RemoteDatabaseEngine = "mysql" | "sqlite";
|
|
|
|
export interface GamePluginRemoteAccess {
|
|
methods: RemoteAccessMethod[];
|
|
runCapabilities?: Array<Extract<RunCapability, `remote.${string}`>>;
|
|
databaseEngines?: RemoteDatabaseEngine[];
|
|
rcon?: boolean;
|
|
logTransfer?: boolean;
|
|
}
|
|
|
|
export type GameClientBridgeApprovalLevel = "none" | "operator" | "platform-admin";
|
|
export type GameClientBridgeApprovalState = "not_required" | "pending" | "approved" | "rejected";
|
|
export type GameClientBridgeCommandState = "pending" | "claimed" | "succeeded" | "failed" | "unknown" | "cancelled" | "expired";
|
|
|
|
export type GameClientBridgeProtectedRequestKind = "sql" | "rcon" | "program";
|
|
|
|
export interface GameClientBridgeProtectedRequestDeclaration {
|
|
kind: GameClientBridgeProtectedRequestKind;
|
|
transportKey: string;
|
|
targetKey: string;
|
|
textField: string;
|
|
maxTextBytes: number;
|
|
}
|
|
|
|
export interface GameClientBridgeCommandDeclaration {
|
|
type: string;
|
|
title: string;
|
|
permission: PluginPermission;
|
|
approvalLevel: GameClientBridgeApprovalLevel;
|
|
payloadSchemaRef: string;
|
|
resultSchemaRef?: string;
|
|
timeoutSeconds: number;
|
|
maxPayloadBytes: number;
|
|
protectedRequest?: GameClientBridgeProtectedRequestDeclaration;
|
|
}
|
|
|
|
export interface GameClientBridgeSnapshotDeclaration {
|
|
type: string;
|
|
schemaVersion: string;
|
|
schemaRef: string;
|
|
keepForSeconds: number;
|
|
maxRecords: number;
|
|
}
|
|
|
|
export interface GameClientBridgeQueryTemplateDeclaration {
|
|
key: string;
|
|
title: string;
|
|
permission: PluginPermission;
|
|
engine: "sqlite";
|
|
transportKey: string;
|
|
targetKey: string;
|
|
parameterSchemaRef: string;
|
|
resultSchemaRef: string;
|
|
maxRows: number;
|
|
timeoutSeconds: number;
|
|
}
|
|
|
|
export type GameClientBridgeOperationKind = "rcon" | "sqlite-mutation";
|
|
|
|
export type SCUMLiveDataCapability =
|
|
| "schema-probe"
|
|
| "players.read"
|
|
| "player-details.read"
|
|
| "squads.read"
|
|
| "squad-members.read"
|
|
| "vehicles.read"
|
|
| "flags.read"
|
|
| "positions.read"
|
|
| "profile-xml.write"
|
|
| "economy-command.write"
|
|
| "gift-command.write";
|
|
|
|
export type SCUMLiveDataGateState = "disabled" | "enabled";
|
|
export type SCUMLiveDataEvidenceStatus = "missing" | "compatible" | "incompatible" | "failed";
|
|
|
|
export interface SCUMLiveDataCapabilityGateDeclaration {
|
|
capability: SCUMLiveDataCapability;
|
|
gate: SCUMLiveDataGateState;
|
|
adapterVersion: string;
|
|
requiredSchemaFingerprint?: string;
|
|
requiredAssetDigests?: `sha256:${string}`[];
|
|
evidenceStatus: SCUMLiveDataEvidenceStatus;
|
|
safeReason: string;
|
|
}
|
|
|
|
export interface SCUMSchemaProbeBoundsDeclaration {
|
|
maxObjects: number;
|
|
maxColumnsPerObject: number;
|
|
maxIndexesPerObject: number;
|
|
maxForeignKeys: number;
|
|
maxCardinalityReads: number;
|
|
maxSampleRows: number;
|
|
timeoutMs: number;
|
|
maxResultBytes: number;
|
|
}
|
|
|
|
export interface SCUMSchemaProbeDeclaration {
|
|
capability: Extract<RunCapability, "remote.run.db.sqlite.probe">;
|
|
targetKey: string;
|
|
bounds: SCUMSchemaProbeBoundsDeclaration;
|
|
}
|
|
|
|
export type SCUMVersionedAssetDigest = `sha256:${string}`;
|
|
export type SCUMLiveDataReadCapability = Extract<SCUMLiveDataCapability, "players.read" | "player-details.read" | "squads.read" | "squad-members.read" | "vehicles.read" | "flags.read" | "positions.read">;
|
|
export type SCUMLiveDataWriteCapability = Extract<SCUMLiveDataCapability, "profile-xml.write" | "economy-command.write" | "gift-command.write">;
|
|
|
|
export interface SCUMVersionedAssetDeclaration {
|
|
key: string;
|
|
adapterVersion: string;
|
|
assetPath: string;
|
|
digest: SCUMVersionedAssetDigest;
|
|
}
|
|
|
|
export interface SCUMLoginLogParserDeclaration extends SCUMVersionedAssetDeclaration {
|
|
parserVersion: string;
|
|
sourceKey: string;
|
|
eventType: string;
|
|
eventSchemaRef: string;
|
|
maxLineBytes: number;
|
|
cursorPolicy: "source-generation-sequence";
|
|
privacy: { stripNetworkIdentifiers: true; logicalEventIdentity: "native-or-sanitized-fields" };
|
|
}
|
|
|
|
export interface SCUMSQLiteQueryAssetDeclaration extends SCUMVersionedAssetDeclaration {
|
|
capability: SCUMLiveDataReadCapability;
|
|
requiredSchemaFingerprint: string;
|
|
transportKey: string;
|
|
targetKey: string;
|
|
parameterSchemaRef: string;
|
|
resultSchemaRef: string;
|
|
maxRows: number;
|
|
timeoutMs: number;
|
|
maxResultBytes: number;
|
|
}
|
|
|
|
export interface SCUMLiveDataSyncCadenceDeclaration {
|
|
capability: SCUMLiveDataReadCapability;
|
|
intervalSeconds: number;
|
|
jitterPercent: number;
|
|
timeoutMs: number;
|
|
maxConcurrentPerServer: number;
|
|
}
|
|
|
|
export interface SCUMTypedRCONTemplateDeclaration extends SCUMVersionedAssetDeclaration {
|
|
capability: Extract<SCUMLiveDataWriteCapability, "economy-command.write" | "gift-command.write">;
|
|
requiredSchemaFingerprint?: string;
|
|
transportKey: string;
|
|
targetKey: string;
|
|
permission: Extract<PluginPermission, "server.game-client.command">;
|
|
payloadSchemaRef: string;
|
|
resultSchemaRef: string;
|
|
confirmationSchemaRef?: string;
|
|
timeoutMs: number;
|
|
maxPayloadBytes: number;
|
|
}
|
|
|
|
export interface SCUMGuardedMutationDeclaration extends SCUMVersionedAssetDeclaration {
|
|
capability: Extract<SCUMLiveDataWriteCapability, "profile-xml.write">;
|
|
requiredSchemaFingerprint: string;
|
|
transportKey: string;
|
|
targetKey: string;
|
|
permission: Extract<PluginPermission, "server.game-client.maintenance">;
|
|
payloadSchemaRef: string;
|
|
resultSchemaRef: string;
|
|
confirmationSchemaRef: string;
|
|
timeoutMs: number;
|
|
maxPayloadBytes: number;
|
|
maxRowsAffected: 1;
|
|
safety: { requiresExpectedChecksum: true; requiresBackupEvidence: true; requiresOfflineOrMaintenance: true; requiresReadAfterWrite: true };
|
|
}
|
|
|
|
export interface SCUMMapAssetDeclaration extends SCUMVersionedAssetDeclaration {
|
|
requiredSchemaFingerprint: string;
|
|
metadataSchemaRef: string;
|
|
transformAssetPath: string;
|
|
transformDigest: SCUMVersionedAssetDigest;
|
|
worldBounds: { minX: number; minY: number; maxX: number; maxY: number };
|
|
image: { width: number; height: number };
|
|
}
|
|
|
|
export interface SCUMGiftCatalogDeclaration extends SCUMVersionedAssetDeclaration {
|
|
catalogVersion: string;
|
|
itemSchemaRef: string;
|
|
transportTemplateKeys: string[];
|
|
}
|
|
|
|
export interface SCUMLiveDataManifestDeclaration {
|
|
schemaVersion: "1";
|
|
probe: SCUMSchemaProbeDeclaration;
|
|
capabilityGates: SCUMLiveDataCapabilityGateDeclaration[];
|
|
logParsers?: SCUMLoginLogParserDeclaration[];
|
|
sqliteQueries?: SCUMSQLiteQueryAssetDeclaration[];
|
|
syncCadences?: SCUMLiveDataSyncCadenceDeclaration[];
|
|
typedRconTemplates?: SCUMTypedRCONTemplateDeclaration[];
|
|
guardedMutations?: SCUMGuardedMutationDeclaration[];
|
|
mapAssets?: SCUMMapAssetDeclaration[];
|
|
giftCatalogs?: SCUMGiftCatalogDeclaration[];
|
|
}
|
|
|
|
export interface GameClientBridgeOperationSafety {
|
|
requiresApproval?: boolean;
|
|
requiresOfflinePlayer?: boolean;
|
|
requiresMaintenanceWindow?: boolean;
|
|
requiresBeforeValue?: boolean;
|
|
requiresConfirmation?: boolean;
|
|
backupRequired?: boolean;
|
|
}
|
|
|
|
export interface GameClientBridgeOperationMutationDeclaration {
|
|
fieldKey: string;
|
|
tableKey: string;
|
|
identityKey: string;
|
|
valueKey: string;
|
|
confirmationQueryKey: string;
|
|
allowedValueType: "integer" | "number" | "string" | "boolean";
|
|
minValue?: number;
|
|
maxValue?: number;
|
|
}
|
|
|
|
export interface GameClientBridgeOperationTemplateDeclaration {
|
|
key: string;
|
|
title: string;
|
|
permission: PluginPermission;
|
|
approvalLevel: Exclude<GameClientBridgeApprovalLevel, "none">;
|
|
kind: GameClientBridgeOperationKind;
|
|
transportKey: string;
|
|
targetKey: string;
|
|
payloadSchemaRef: string;
|
|
resultSchemaRef?: string;
|
|
confirmationSchemaRef?: string;
|
|
timeoutSeconds: number;
|
|
maxPayloadBytes: number;
|
|
maxRowsAffected?: number;
|
|
mutation?: GameClientBridgeOperationMutationDeclaration;
|
|
safety?: GameClientBridgeOperationSafety;
|
|
}
|
|
|
|
export interface GameClientBridgePageContract {
|
|
pageKey: string;
|
|
commandTypes?: string[];
|
|
snapshotTypes?: string[];
|
|
queryTemplateKeys?: string[];
|
|
operationKeys?: string[];
|
|
featureKeys?: string[];
|
|
}
|
|
|
|
export interface GameClientBridgeFeatureDeclaration { key: string; title: string; permission: PluginPermission; requiredHandlers?: string[]; requiredEventProducers?: string[]; }
|
|
|
|
export interface GameClientBridgeCompanionDeclaration {
|
|
profileKey: string;
|
|
configTemplateKey: string;
|
|
configSchemaRef: string;
|
|
configFormat: "yaml";
|
|
platformBaseUrlSource: "run-control";
|
|
registrationProof: "hmac-sha256";
|
|
proofMaterialSource: "component-package";
|
|
proofMaterialEnv: string;
|
|
sessionMode: "component-session";
|
|
tlsPolicy: "verify-system-roots";
|
|
heartbeatIntervalSeconds: number;
|
|
commandPollIntervalSeconds: number;
|
|
requestTimeoutSeconds: number;
|
|
}
|
|
|
|
export interface GameClientBridgeManifest {
|
|
commands: GameClientBridgeCommandDeclaration[];
|
|
snapshots: GameClientBridgeSnapshotDeclaration[];
|
|
queryTemplates?: GameClientBridgeQueryTemplateDeclaration[];
|
|
operationTemplates?: GameClientBridgeOperationTemplateDeclaration[];
|
|
commandRetentionSeconds: number;
|
|
maxCommands: number;
|
|
pages?: GameClientBridgePageContract[];
|
|
features?: GameClientBridgeFeatureDeclaration[];
|
|
companion?: GameClientBridgeCompanionDeclaration;
|
|
}
|
|
|
|
export interface GameClientBridgeProfileStatus {
|
|
pluginId: string;
|
|
profileKey: string;
|
|
available: boolean;
|
|
reason?: string;
|
|
commandTypes: string[];
|
|
snapshotTypes: string[];
|
|
queryTemplateKeys: string[];
|
|
handlerTypes?: string[];
|
|
eventProducerTypes?: string[];
|
|
}
|
|
|
|
export interface GameClientBridgeFeatureAvailability { key: string; available: boolean; reason?: string; }
|
|
|
|
export interface GameClientBridgeStatus {
|
|
serverInstanceId: string;
|
|
pluginId: string;
|
|
available: boolean;
|
|
reason?: string;
|
|
profiles: GameClientBridgeProfileStatus[];
|
|
features?: GameClientBridgeFeatureAvailability[];
|
|
}
|
|
|
|
export interface GameClientBridgeCommandResult {
|
|
status: "succeeded" | "failed" | "unknown" | "cancelled";
|
|
summary?: string;
|
|
payload?: Record<string, unknown>;
|
|
completedAt: string;
|
|
}
|
|
|
|
export interface GameClientBridgeCommand {
|
|
id: string;
|
|
serverInstanceId: string;
|
|
pluginId: string;
|
|
profileKey: string;
|
|
commandType: string;
|
|
priority: number;
|
|
state: GameClientBridgeCommandState;
|
|
approvalState: GameClientBridgeApprovalState;
|
|
result?: GameClientBridgeCommandResult;
|
|
auditReferences?: string[];
|
|
expiresAt: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
completedAt?: string;
|
|
}
|
|
|
|
export interface GameClientBridgeSnapshot<TPayload extends Record<string, unknown> = Record<string, unknown>> {
|
|
id: string;
|
|
serverInstanceId: string;
|
|
pluginId: string;
|
|
profileKey: string;
|
|
type: string;
|
|
schemaVersion: string;
|
|
streamKey: string;
|
|
sequence: number;
|
|
observedAt: string;
|
|
payload: TPayload;
|
|
auditReferences?: string[];
|
|
createdAt: string;
|
|
expiresAt: string;
|
|
}
|
|
|
|
export interface GameClientBridgeQueueRequest<TPayload extends Record<string, unknown> = Record<string, unknown>> {
|
|
profileKey: string;
|
|
commandType: string;
|
|
payload: TPayload;
|
|
idempotencyKey: string;
|
|
priority?: number;
|
|
expiresAt: string;
|
|
}
|
|
|
|
export interface GameClientBridgeSnapshotQuery {
|
|
profileKey?: string;
|
|
type?: string;
|
|
streamKey?: string;
|
|
observedAfter?: string;
|
|
limit?: number;
|
|
}
|
|
|
|
export interface GameClientBridgePageClient {
|
|
getStatus(serverInstanceId: string): Promise<GameClientBridgeStatus>;
|
|
listCommands(serverInstanceId: string): Promise<GameClientBridgeCommand[]>;
|
|
queueCommand<TPayload extends Record<string, unknown>>(serverInstanceId: string, request: GameClientBridgeQueueRequest<TPayload>): Promise<GameClientBridgeCommand>;
|
|
cancelCommand(serverInstanceId: string, commandId: string, reason?: string): Promise<GameClientBridgeCommand>;
|
|
querySnapshots<TPayload extends Record<string, unknown> = Record<string, unknown>>(serverInstanceId: string, query?: GameClientBridgeSnapshotQuery): Promise<Array<GameClientBridgeSnapshot<TPayload>>>;
|
|
}
|
|
|
|
export function createGameClientBridgeQueueRequest<TPayload extends Record<string, unknown>>(input: GameClientBridgeQueueRequest<TPayload>): GameClientBridgeQueueRequest<TPayload> {
|
|
if (!input.profileKey || !input.commandType || !input.idempotencyKey || !input.expiresAt) {
|
|
throw new Error("profileKey, commandType, idempotencyKey, and expiresAt are required");
|
|
}
|
|
return { profileKey: input.profileKey, commandType: input.commandType, payload: { ...input.payload }, idempotencyKey: input.idempotencyKey, priority: input.priority, expiresAt: input.expiresAt };
|
|
}
|
|
|
|
export type RuntimePlatform = "windows" | "linux" | "darwin";
|
|
export type RuntimeArch = "amd64" | "arm64";
|
|
export type RuntimeTarget = { os: RuntimePlatform; arch: RuntimeArch };
|
|
|
|
export interface RuntimeDiscoveryProbe {
|
|
key: string;
|
|
kind: "file.exists" | "command.version" | "service.status" | "port.open" | "steam.app" | "docker.container";
|
|
targetKey: string;
|
|
required?: boolean;
|
|
expected?: string;
|
|
platforms?: RuntimePlatform[];
|
|
}
|
|
|
|
export interface RuntimeLifecycleProfile {
|
|
key: string;
|
|
mode: "local-process" | "hosted-ftp-rcon" | "ftp-only" | "custom-client";
|
|
capabilities: RunCapability[];
|
|
actionRefs?: Partial<Record<PluginLifecycleAction, string>>;
|
|
transportKeys?: string[];
|
|
clientManagerRef?: string;
|
|
dllExtensionRefs?: string[];
|
|
platforms?: RuntimePlatform[];
|
|
}
|
|
|
|
export interface RuntimeDependencyProbe {
|
|
key: string;
|
|
kind: "command.version" | "service.exists" | "port.available" | "steam.app" | "java.version" | "docker.available" | "package.installed" | "file.exists";
|
|
targetKey: string;
|
|
required?: boolean;
|
|
minimumVersion?: string;
|
|
platforms?: RuntimePlatform[];
|
|
}
|
|
|
|
export interface RuntimeInstallStep {
|
|
type: "package" | "verified-download" | "steamcmd-app" | "manual";
|
|
targetKey: string;
|
|
packageManager?: "winget" | "choco" | "scoop" | "apt" | "yum" | "dnf" | "pacman" | "zypper" | "brew" | "steamcmd" | "manual";
|
|
packageName?: string;
|
|
version?: string;
|
|
downloadRef?: string;
|
|
checksum?: `sha256:${string}`;
|
|
}
|
|
|
|
export interface RuntimeInstallPlan {
|
|
key: string;
|
|
title: string;
|
|
platforms?: RuntimePlatform[];
|
|
steps: RuntimeInstallStep[];
|
|
}
|
|
|
|
export interface RuntimeLogSource {
|
|
key: string;
|
|
kind: "process.stdout" | "process.stderr" | "file.tail" | "ftp.poll" | "sql.query" | "client-manager";
|
|
targetKey?: string;
|
|
streamKey: string;
|
|
cursorKind?: "sequence" | "offset" | "fingerprint" | "ftp-listing" | "sql-cursor";
|
|
retentionDays?: number;
|
|
}
|
|
|
|
export type RuntimeLogEventSeverity = "info" | "notice" | "warning" | "critical";
|
|
|
|
export interface RuntimeLogEventDeclaration {
|
|
key: string;
|
|
title: string;
|
|
sourceKey: string;
|
|
eventType: string;
|
|
permission: PluginPermission;
|
|
schemaRef: string;
|
|
retentionDays: number;
|
|
severity: RuntimeLogEventSeverity;
|
|
}
|
|
|
|
export interface RuntimeTransportProfile {
|
|
key: string;
|
|
kind: "file" | "ftp" | "rsync" | "mysql" | "sqlite" | "rcon" | "program";
|
|
targetKey?: string;
|
|
capabilities: RunCapability[];
|
|
}
|
|
|
|
export interface RuntimeDataTargetDeclaration {
|
|
key: string;
|
|
kind: "sqlite.snapshot";
|
|
transportKey: string;
|
|
sourceRootKey: string;
|
|
sourcePath: string;
|
|
workspaceKey: string;
|
|
refreshPolicy: "on-demand-snapshot";
|
|
maxBytes: number;
|
|
platforms?: RuntimePlatform[];
|
|
}
|
|
|
|
export interface RuntimeClientManagerProfile {
|
|
key: string;
|
|
displayName?: string;
|
|
version?: string;
|
|
repository: {
|
|
url: string;
|
|
revisionPolicy: "pinned" | "branch" | "tag";
|
|
branch?: string;
|
|
tag?: string;
|
|
revision?: string;
|
|
};
|
|
supportedTargets: RuntimeTarget[];
|
|
build: {
|
|
system: "go" | "npm" | "cargo" | "make";
|
|
workspaceRef?: string;
|
|
entryRef?: string;
|
|
};
|
|
configTemplates?: Array<{ key: string; templateRef: string; outputRef: string }>;
|
|
outputArtifacts: string[];
|
|
deployment?: {
|
|
mode: "run-supervised";
|
|
executableRef: string;
|
|
arguments?: string[];
|
|
autoStart?: boolean;
|
|
requiredRunCapabilities: Array<"client-manager.deploy" | "client-manager.control" | "client-manager.update" | "client-manager.rollback" | "client-manager.uninstall">;
|
|
};
|
|
lifecycle?: {
|
|
actions: Array<"start" | "stop" | "restart" | "status" | "update" | "rollback" | "uninstall">;
|
|
startupTimeoutSeconds: number;
|
|
stopTimeoutSeconds: number;
|
|
};
|
|
health?: {
|
|
mode: "component-heartbeat" | "process";
|
|
intervalSeconds: number;
|
|
degradedAfterSeconds: number;
|
|
offlineAfterSeconds: number;
|
|
requiredCapabilities: Array<"component.register" | "component.heartbeat" | "component.health" | "component.control" | "game-client.bridge" | "logs.stream">;
|
|
};
|
|
compatibility?: {
|
|
minimumVersion?: string;
|
|
maximumVersion?: string;
|
|
allowDowngrade: boolean;
|
|
};
|
|
updatePolicy?: {
|
|
strategy: "manual-staged";
|
|
requireApproval: true;
|
|
healthConfirmationSeconds: number;
|
|
retainPrevious: true;
|
|
};
|
|
}
|
|
|
|
export interface RuntimeDLLExtensionProfile {
|
|
key: string;
|
|
displayName: string;
|
|
kind: "ue4ss-dll";
|
|
activation: "server-start";
|
|
version: string;
|
|
releaseState: "unpublished" | "ready";
|
|
releaseUrl?: string;
|
|
checksum?: `sha256:${string}`;
|
|
sizeBytes?: number;
|
|
targetKey: string;
|
|
modKey: string;
|
|
dllRef: string;
|
|
scumExecutableChecksum?: `sha256:${string}`;
|
|
ue4ssAbi?: string;
|
|
supportedTargets: [{ os: "windows"; arch: "amd64" }];
|
|
updateOnStart: true;
|
|
rconPort: number;
|
|
}
|
|
|
|
export interface GamePluginRuntimeProfiles {
|
|
discovery?: RuntimeDiscoveryProbe[];
|
|
lifecycleProfiles?: RuntimeLifecycleProfile[];
|
|
dependencyProbes?: RuntimeDependencyProbe[];
|
|
installPlans?: RuntimeInstallPlan[];
|
|
logSources?: RuntimeLogSource[];
|
|
logEvents?: RuntimeLogEventDeclaration[];
|
|
transportProfiles?: RuntimeTransportProfile[];
|
|
dataTargets?: RuntimeDataTargetDeclaration[];
|
|
clientManagers?: RuntimeClientManagerProfile[];
|
|
dllExtensions?: RuntimeDLLExtensionProfile[];
|
|
}
|
|
|
|
export interface PluginArtifactReference {
|
|
artifactId: string;
|
|
filename: string;
|
|
contentType: string;
|
|
sizeBytes: number;
|
|
checksum: string;
|
|
downloadUrl: string;
|
|
expiresAt: string;
|
|
rangeSupported: boolean;
|
|
chunkSizeBytes: number;
|
|
storageBehavior?: string;
|
|
}
|
|
|
|
export interface PluginClientManagerLifecycleStatus {
|
|
installationId: string;
|
|
profileKey: string;
|
|
status: string;
|
|
phase?: string;
|
|
targetOS?: RuntimePlatform;
|
|
targetArch?: RuntimeArch;
|
|
version?: string;
|
|
previousVersion?: string;
|
|
artifactId?: string;
|
|
currentJobId?: string;
|
|
deploymentGeneration?: number;
|
|
health?: string;
|
|
healthReason?: string;
|
|
lastSeenAt?: string;
|
|
actions: string[];
|
|
}
|
|
|
|
export type PluginBridgeExecutionResponse<TResult extends Record<string, string> = Record<string, string>> = {
|
|
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<PluginBridgeAction, PluginBridgeActionPolicy> = {
|
|
"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"] },
|
|
"remote.access.request": { permissions: ["server.remote.access"] },
|
|
"run.distribution.request": { permissions: ["server.run.distribution"] },
|
|
"dependencies.request": { permissions: ["server.dependencies.manage"] },
|
|
"logs.backfill.request": { permissions: ["server.logs.read"] },
|
|
"client-manager.request": { permissions: ["server.client-manager.manage"] },
|
|
"plugin-lifecycle.request": { permissions: ["server.lifecycle"] },
|
|
"ai.invoke": { permissions: ["ai.invoke"], aiPurposeRequired: true }
|
|
};
|
|
|
|
export type PluginLifecycleAction = "install" | "start" | "stop" | "restart" | "status";
|
|
|
|
export interface PluginLifecycleActionDeclaration {
|
|
version: 1;
|
|
action: PluginLifecycleAction;
|
|
mode: "oneshot" | "supervised" | "control";
|
|
executableKey?: string;
|
|
arguments?: string[];
|
|
environment?: Record<`GAME_${string}` | `SERVER_${string}` | `RUN_${string}`, string>;
|
|
timeoutMs?: number;
|
|
stopTimeoutMs?: number;
|
|
}
|
|
|
|
export type GamePluginActions = Partial<Record<PluginLifecycleAction, string>> & {
|
|
install: string;
|
|
start: string;
|
|
stop: string;
|
|
};
|
|
|
|
export interface GamePluginPage {
|
|
key: string;
|
|
title: string;
|
|
path: string;
|
|
bundleKey: string;
|
|
bundleVersion: string;
|
|
bundleIntegritySha256: `sha256:${string}`;
|
|
permissions?: PluginPermission[];
|
|
bridgeActions?: PluginBridgeAction[];
|
|
}
|
|
|
|
export interface GamePluginBridge {
|
|
actions: PluginBridgeAction[];
|
|
}
|
|
|
|
export interface PluginAssetFile {
|
|
path: string;
|
|
mode?: 384 | 448;
|
|
}
|
|
|
|
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[];
|
|
remoteAccess?: GamePluginRemoteAccess;
|
|
runtimeProfiles?: GamePluginRuntimeProfiles;
|
|
gameClientBridge?: GameClientBridgeManifest;
|
|
scumLiveData?: SCUMLiveDataManifestDeclaration;
|
|
actions?: GamePluginActions;
|
|
assetFiles?: PluginAssetFile[];
|
|
productionLifecycle: {
|
|
operations: ProductionPluginLifecycleOperation[];
|
|
dependencyPolicy: "required" | "optional";
|
|
approvalRequired: Array<"disable" | "rollback" | "retire">;
|
|
};
|
|
pages?: GamePluginPage[];
|
|
ai?: {
|
|
purposes?: AIPurpose[];
|
|
mediation: "platform";
|
|
configWritePolicy: "review-required";
|
|
};
|
|
}
|
|
|
|
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<TPayload extends PluginBridgeRequestPayload>(input: {
|
|
id: string;
|
|
context: PluginBridgeContext;
|
|
action: PluginBridgeAction;
|
|
payload: TPayload;
|
|
}): PluginBridgeRequest<TPayload> {
|
|
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<TPayload extends Record<string, string>>(input: {
|
|
requestId: string;
|
|
context: PluginBridgeContext;
|
|
action: PluginBridgeAction;
|
|
aiPurpose?: AIPurpose;
|
|
payload?: TPayload;
|
|
}): PluginBridgeExecutionRequest<TPayload> {
|
|
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<PluginArtifactOpenPayload> {
|
|
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<PluginLifecycleDispatchPayload> {
|
|
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 createProductionPluginLifecycleRequest(input: {
|
|
requestId: string;
|
|
context: PluginBridgeContext;
|
|
operation: ProductionPluginLifecycleOperation;
|
|
targetVersion?: string;
|
|
idempotencyKey: string;
|
|
confirmed?: boolean;
|
|
}): PluginBridgeExecutionRequest<PluginProductionLifecyclePayload> {
|
|
if (!input.context.serverInstanceId) {
|
|
throw new Error("serverInstanceId is required for plugin lifecycle requests");
|
|
}
|
|
if (["disable", "rollback", "retire"].includes(input.operation) && !input.confirmed) {
|
|
throw new Error("disruptive plugin lifecycle requests require confirmation");
|
|
}
|
|
return createBridgeExecutionRequest({
|
|
requestId: input.requestId,
|
|
context: input.context,
|
|
action: "plugin-lifecycle.request",
|
|
payload: {
|
|
operation: input.operation,
|
|
targetVersion: input.targetVersion ?? "",
|
|
idempotencyKey: input.idempotencyKey,
|
|
confirmed: input.confirmed ? "true" : "false"
|
|
}
|
|
});
|
|
}
|
|
|
|
export function createRemoteAccessRequest(input: {
|
|
requestId: string;
|
|
context: PluginBridgeContext;
|
|
capability: PluginRemoteAccessPayload["capability"];
|
|
targetKey?: string;
|
|
inputRef?: string;
|
|
inputs?: Record<string, string>;
|
|
idempotencyKey: string;
|
|
}): PluginBridgeExecutionRequest<PluginRemoteAccessPayload> {
|
|
const payload: PluginRemoteAccessPayload = {
|
|
capability: input.capability,
|
|
targetKey: input.targetKey ?? "",
|
|
inputRef: input.inputRef ?? "",
|
|
idempotencyKey: input.idempotencyKey
|
|
};
|
|
for (const [key, value] of Object.entries(input.inputs ?? {})) {
|
|
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/.test(key) || /(sql|query|shell|script|password|secret|token|credential|dsn|path)/i.test(key)) {
|
|
throw new Error(`remote adapter input key is unsafe: ${key}`);
|
|
}
|
|
payload[`input.${key}`] = value;
|
|
}
|
|
return createBridgeExecutionRequest({
|
|
requestId: input.requestId,
|
|
context: input.context,
|
|
action: "remote.access.request",
|
|
payload
|
|
});
|
|
}
|
|
|
|
export function createRunDistributionRequest(input: {
|
|
requestId: string;
|
|
context: PluginBridgeContext;
|
|
operation: PluginRunDistributionPayload["operation"];
|
|
targetOS?: RuntimePlatform;
|
|
targetArch?: RuntimeArch;
|
|
artifactId?: string;
|
|
idempotencyKey: string;
|
|
}): PluginBridgeExecutionRequest<PluginRunDistributionPayload> {
|
|
const payload: PluginRunDistributionPayload = {
|
|
operation: input.operation,
|
|
artifactId: input.artifactId ?? "",
|
|
idempotencyKey: input.idempotencyKey
|
|
};
|
|
if (input.targetOS) {
|
|
payload.targetOS = input.targetOS;
|
|
}
|
|
if (input.targetArch) {
|
|
payload.targetArch = input.targetArch;
|
|
}
|
|
return createBridgeExecutionRequest({
|
|
requestId: input.requestId,
|
|
context: input.context,
|
|
action: "run.distribution.request",
|
|
payload
|
|
});
|
|
}
|
|
|
|
export function createDependencyActionRequest(input: {
|
|
requestId: string;
|
|
context: PluginBridgeContext;
|
|
operation: PluginDependencyActionPayload["operation"];
|
|
probeKey?: string;
|
|
planKey?: string;
|
|
planDigest?: string;
|
|
idempotencyKey: string;
|
|
}): PluginBridgeExecutionRequest<PluginDependencyActionPayload> {
|
|
if (input.operation === "install" && !/^sha256:[a-fA-F0-9]{64}$/.test(input.planDigest ?? "")) {
|
|
throw new Error("dependency install requires the reviewed plan SHA-256 digest");
|
|
}
|
|
return createBridgeExecutionRequest({
|
|
requestId: input.requestId,
|
|
context: input.context,
|
|
action: "dependencies.request",
|
|
payload: {
|
|
operation: input.operation,
|
|
probeKey: input.probeKey ?? "",
|
|
planKey: input.planKey ?? "",
|
|
planDigest: input.planDigest ?? "",
|
|
idempotencyKey: input.idempotencyKey
|
|
}
|
|
});
|
|
}
|
|
|
|
export function createLogBackfillRequest(input: {
|
|
requestId: string;
|
|
context: PluginBridgeContext;
|
|
sourceKey: string;
|
|
cursor?: string;
|
|
limit?: number;
|
|
idempotencyKey: string;
|
|
}): PluginBridgeExecutionRequest<PluginLogBackfillPayload> {
|
|
return createBridgeExecutionRequest({
|
|
requestId: input.requestId,
|
|
context: input.context,
|
|
action: "logs.backfill.request",
|
|
payload: {
|
|
sourceKey: input.sourceKey,
|
|
cursor: input.cursor ?? "",
|
|
limit: typeof input.limit === "number" ? String(input.limit) : "",
|
|
idempotencyKey: input.idempotencyKey
|
|
}
|
|
});
|
|
}
|
|
|
|
export function createClientManagerRequest(input: {
|
|
requestId: string;
|
|
context: PluginBridgeContext;
|
|
operation: PluginClientManagerPayload["operation"];
|
|
profileKey: string;
|
|
targetOS?: RuntimePlatform;
|
|
targetArch?: RuntimeArch;
|
|
artifactId?: string;
|
|
installationId?: string;
|
|
expectedDeploymentGeneration?: number;
|
|
idempotencyKey: string;
|
|
}): PluginBridgeExecutionRequest<PluginClientManagerPayload> {
|
|
const payload: PluginClientManagerPayload = {
|
|
operation: input.operation,
|
|
profileKey: input.profileKey,
|
|
artifactId: input.artifactId ?? "",
|
|
installationId: input.installationId ?? "",
|
|
expectedDeploymentGeneration: typeof input.expectedDeploymentGeneration === "number" ? String(input.expectedDeploymentGeneration) : "",
|
|
idempotencyKey: input.idempotencyKey
|
|
};
|
|
if (input.targetOS) {
|
|
payload.targetOS = input.targetOS;
|
|
}
|
|
if (input.targetArch) {
|
|
payload.targetArch = input.targetArch;
|
|
}
|
|
return createBridgeExecutionRequest({
|
|
requestId: input.requestId,
|
|
context: input.context,
|
|
action: "client-manager.request",
|
|
payload
|
|
});
|
|
}
|
|
|
|
export function parseArtifactReference(result: Record<string, string> | 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 parseClientManagerLifecycleStatus(result: Record<string, string> | undefined): PluginClientManagerLifecycleStatus | undefined {
|
|
if (!result) {
|
|
return undefined;
|
|
}
|
|
const deploymentGeneration = Number(result.deploymentGeneration ?? "0");
|
|
const values = [
|
|
result.installationId,
|
|
result.profileKey,
|
|
result.status,
|
|
result.phase,
|
|
result.targetOS,
|
|
result.targetArch,
|
|
result.version,
|
|
result.previousVersion,
|
|
result.artifactId,
|
|
result.currentJobId,
|
|
result.health,
|
|
result.healthReason,
|
|
result.lastSeenAt,
|
|
result.actions
|
|
];
|
|
if (!result.installationId || !result.profileKey || !result.status || !Number.isSafeInteger(deploymentGeneration) || deploymentGeneration < 0) {
|
|
return undefined;
|
|
}
|
|
if (values.some((value) => typeof value === "string" && containsUnsafeReferenceContent(value))) {
|
|
return undefined;
|
|
}
|
|
return {
|
|
installationId: result.installationId,
|
|
profileKey: result.profileKey,
|
|
status: result.status,
|
|
phase: result.phase,
|
|
targetOS: result.targetOS as RuntimePlatform | undefined,
|
|
targetArch: result.targetArch as RuntimeArch | undefined,
|
|
version: result.version,
|
|
previousVersion: result.previousVersion,
|
|
artifactId: result.artifactId,
|
|
currentJobId: result.currentJobId,
|
|
deploymentGeneration,
|
|
health: result.health,
|
|
healthReason: result.healthReason,
|
|
lastSeenAt: result.lastSeenAt,
|
|
actions: (result.actions ?? "").split(",").filter(Boolean)
|
|
};
|
|
}
|
|
|
|
export function parseBridgeExecutionResponse<TResult extends Record<string, string>>(response: PluginBridgeExecutionResponse<TResult>): PluginBridgeExecutionResponse<TResult> {
|
|
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<string, string>;
|
|
}): 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,
|
|
configRecommendation: response.configRecommendation ? { ...response.configRecommendation } : undefined,
|
|
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.includes("sessiontoken") ||
|
|
lowered.includes("secret://") ||
|
|
lowered.includes("hostpath") ||
|
|
lowered.includes("processid") ||
|
|
lowered.startsWith("sk-")
|
|
);
|
|
}
|