import type { NapiSandboxModifyOptions } from "./internal/napi.js"; // The wire contract for these shapes is generated from the Rust types in // packages/microsandbox-types (see packages/microsandbox-types/typescript/src/index.ts, // generated by `cargo run -p microsandbox-types --features ts --bin microsandbox-types-generate`). // This module mirrors those shapes with the node SDK's camelCase field names; keep the // literal unions below in sync with the generated `@microsandbox/types` declarations. /** Policy for applying a sandbox modification. */ export type ModificationPolicy = "no_restart" | "next_start" | "restart"; /** When or whether a planned change can take effect. */ export type ModificationDisposition = | "live" | "next start" | "requires restart" | "unsupported"; /** Natural config change type for human output. */ export type ChangeKind = "added" | "updated" | "removed"; /** Natural secret change type for human output. */ export type SecretChangeKind = | "added" | "rotated" | "removed" | "renamed" | "hosts updated" | "placeholder updated"; /** Resource kind used by live resize convergence reporting. */ export type ResourceKind = "cpus" | "memory"; /** Runtime convergence state for an accepted resource resize. */ export type ResourceConvergenceState = | "accepted" | "converging" | "applied" | "guest-refused" | "failed"; /** * Options for `Sandbox.modify()` / `SandboxHandle.modify()`. * * `memory` / `maxMemory` are in MiB. Omitted fields are left unchanged. */ export interface ModifyOptions { /** Desired effective vCPU count. */ cpus?: number; /** Desired boot-time maximum possible vCPU count. */ maxCpus?: number; /** Desired effective guest memory in MiB. */ memory?: number; /** Desired boot-time maximum hotpluggable memory in MiB. */ maxMemory?: number; /** Environment variables to set for future execs. */ env?: Record; /** Environment variable keys to remove. */ envRemove?: string[]; /** Labels to set. */ labels?: Record; /** Label keys to remove. */ labelsRemove?: string[]; /** Desired working directory for future execs. */ workdir?: string; /** * Desired secret specs, keyed by secret name. The planner diffs each spec * against the existing config to infer what changes; omitting a name never * means removal (see `secretsRemove`). */ secrets?: Record; /** Secret names to remove. */ secretsRemove?: string[]; /** Apply policy. Defaults to `"no_restart"`. */ policy?: ModificationPolicy; /** Compute the plan without applying anything. Defaults to `false`. */ dryRun?: boolean; } /** * Desired state for one secret in `ModifyOptions.secrets`. * * `env`, `value`, and `store` are mutually exclusive ways to provide the * secret material; setting more than one is rejected at the native boundary. * Only `value` may carry raw secret material, and it never appears in the * returned plan. */ export interface SecretModifySpec { /** Resolve the value from this host environment variable at apply time. */ env?: string; /** Raw secret value supplied directly (e.g. from the caller's own vault). */ value?: string; /** Resolve the value from this host-side secret store reference. */ store?: string; /** Guest-visible placeholder/reference, if explicitly requested. */ placeholder?: string; /** * Desired allowed host patterns. Empty leaves an existing secret's hosts * unchanged; a new secret needs at least one. */ allowedHosts?: string[]; } /** Ordinary config change planned by `modify()`. */ export interface ConfigPlannedChange { kind: "config"; field: string; change: ChangeKind; before?: string; after?: string; disposition: ModificationDisposition; reason?: string; } /** Secret change planned by `modify()`. Values are omitted by construction. */ export interface SecretPlannedChange { kind: "secret"; field: string; name: string; change: SecretChangeKind; beforeRef?: string; afterRef?: string; disposition: ModificationDisposition; allowHosts: string[]; reason?: string; } export type PlannedChange = ConfigPlannedChange | SecretPlannedChange; /** Conflict that blocks applying a modification. */ export interface ModificationConflict { field: string; message: string; } /** Warning emitted while planning a modification. */ export interface ModificationWarning { field: string; message: string; } /** Runtime convergence status for a live resource resize. */ export interface ResourceResizeStatus { resource: ResourceKind; requested: string; actual: string; enforced: string; state: ResourceConvergenceState; } /** Dry-run or apply plan returned by `modify()`. */ export interface SandboxModificationPlan { sandbox: string; status: string; applied: boolean; policy: ModificationPolicy; changes: PlannedChange[]; conflicts: ModificationConflict[]; warnings: ModificationWarning[]; resizeStatus: ResourceResizeStatus[]; } /** Map TS modify options onto the native option object. */ export function modifyOptionsToNapi( opts?: ModifyOptions, ): NapiSandboxModifyOptions | undefined { if (!opts) return undefined; return { cpus: opts.cpus, maxCpus: opts.maxCpus, memoryMib: opts.memory, maxMemoryMib: opts.maxMemory, env: opts.env, envRemove: opts.envRemove, labels: opts.labels, labelsRemove: opts.labelsRemove, workdir: opts.workdir, secrets: opts.secrets, secretsRemove: opts.secretsRemove, policy: opts.policy, dryRun: opts.dryRun, }; } /** Parse the canonical plan JSON emitted by the native layer. */ export function modificationPlanFromJson(raw: string): SandboxModificationPlan { const plan = JSON.parse(raw); return { sandbox: plan.sandbox, status: plan.status, applied: plan.applied, policy: plan.policy, changes: (plan.changes ?? []).map(plannedChangeFromJson), conflicts: plan.conflicts ?? [], warnings: plan.warnings ?? [], resizeStatus: plan.resize_status ?? [], }; } function plannedChangeFromJson(change: any): PlannedChange { if (change.kind === "secret") { return { kind: "secret", field: change.field, name: change.name, change: change.change, beforeRef: change.before_ref ?? undefined, afterRef: change.after_ref ?? undefined, disposition: change.disposition, allowHosts: change.allow_hosts ?? [], reason: change.reason ?? undefined, }; } return { kind: "config", field: change.field, change: change.change, before: change.before ?? undefined, after: change.after ?? undefined, disposition: change.disposition, reason: change.reason ?? undefined, }; }