import { type LocalInferenceProfileMode } from "./local-inference-profile.ts"; import { type ManagedRuntimeSpawn, type RuntimeCommandRunner } from "./runtime-process.ts"; export type { RuntimeCommandResult, RuntimeCommandRunner } from "./runtime-process.ts"; /** * Local model runtime manager (local-model-lifecycle-design.md): Ollama first, interface kept * runtime-agnostic. Pi SPAWNS the serve process itself with OWNED model storage * (`OLLAMA_MODELS=/models/ollama`) so every downloaded weight lives inside pi's tree — * per-model disk accounting is trivial and full cleanup is one directory. User-level Ollama stores * can be imported into the owned store, but are never silently served or deleted. * * Hard boundaries (design "Hard boundaries"): lifecycle actions are USER commands only — this * module is never exposed as a model-invokable tool; install is GUIDE MODE (exact manual steps, * never `curl | sh`); removal is explicit, disclosed, and never automatic. */ export type OllamaStoreKind = "pi-owned" | "user" | "external"; export interface OllamaStoreSummary { kind: OllamaStoreKind; path: string; modelCount: number; } export interface LocalRuntimeStatus { binaryPath?: string; binarySource?: "system" | "user" | "pi-owned"; serverUp: boolean; serverUrl: string; /** True when the responding server is a child process pi spawned (owned storage applies). */ managedByPi: boolean; /** Owned weights directory. This is pi's canonical Ollama store. */ ownedModelsDir: string; userModelsDir: string; ownedStore: OllamaStoreSummary; userStore: OllamaStoreSummary; activeStore?: OllamaStoreSummary; /** Models reported by the configured live server; empty when it is down. */ serverModels: InstalledLocalModel[]; } export interface InstalledLocalModel { name: string; sizeBytes: number; } export interface OllamaImportResult { sourceDir: string; targetDir: string; manifestsImported: number; manifestsSkipped: number; blobsHardlinked: number; blobsCopied: number; blobsSkipped: number; bytesImported: number; } export interface OllamaModelInfo { modelInfo: Record; } export type OllamaModelParameter = string | number | boolean; /** Current structured `/api/create` contract. Keep raw Modelfile syntax out of callers so managed * profiles and context sizing cannot drift from the runtime adapter's supported request shape. */ export interface OllamaCreateModelInput { name: string; from: string; template?: string; renderer?: string; parser?: string; parameters?: Readonly>; } /** Pinned ollama release for the managed installer below. Bump here when needed — one constant. */ export declare const OLLAMA_PINNED_VERSION = "0.31.1"; export type OllamaAssetKind = "tar-zst" | "tar-gz" | "zip"; export interface OllamaReleaseAsset { name: string; kind: OllamaAssetKind; } /** * Maps a platform/arch pair (node's `os.platform()`/`os.arch()`) to the exact ollama release asset * name for {@link OLLAMA_PINNED_VERSION} — verified against the real ollama/ollama GitHub release * and its own install.sh, not guessed: darwin ships a CLI `.tgz` (the `.app`/`.dmg`/`Ollama-*.zip` * assets are the separate GUI-app installer, not what we want), linux ships `.tar.zst` per arch, * windows ships `.zip` per arch. Pure and exported so it's independently testable and reusable * (e.g. by a future doctor-driven managed install) without touching OllamaRuntime. */ export declare function resolveOllamaAsset(plat: string, architecture: string): OllamaReleaseAsset | undefined; export interface LocalRuntimeDeps { fetchFn?: typeof fetch; spawnFn?: ManagedRuntimeSpawn; existsFn?: (path: string) => boolean; linkFile?: (source: string, target: string) => void; copyFile?: (source: string, target: string) => void; envPath?: string; homeDir?: string; sleepFn?: (ms: number) => Promise; /** os.platform()/os.arch() equivalents — injectable so asset/runtime resolution is testable per platform. */ platform?: () => string; arch?: () => string; /** Host capacity probes used once to derive the bounded local inference profile. */ totalMemoryBytes?: () => number; logicalCpuCount?: () => number; /** Runs a runtime-management command (Python venv/pip/download probe). Injectable so tests never * install packages or hit the network. */ runCommand?: RuntimeCommandRunner; /** Override for bundled runtime helper scripts, used by tests and source/binary packaging checks. */ transformersServerScriptPath?: string; /** Node's built-in zstd decompress transform, when THIS runtime's node:zlib has it — undefined * forces the system-zstd fallback. Injectable so tests can force either path deterministically * instead of depending on whatever Node happens to be running the test. */ createZstdDecompress?: () => NodeJS.ReadWriteStream; /** Whether a named command exists on PATH (e.g. system `zstd`), for the extraction fallback. */ hasCommand?: (command: string) => boolean; /** Runs the extraction step for a downloaded archive. Injectable so installManaged's * download->extract->verify orchestration is testable without a real tar/zstd/unzip pipeline; * defaults to the real spawn-based extractor. */ extractArchive?: (input: NodeJS.ReadableStream, destDir: string, kind: OllamaAssetKind) => Promise<{ ok: boolean; error?: string; }>; } export declare function resolveTransformersBaseUrl(modelId: string): string; export declare class OllamaRuntime { private readonly _agentDir; private readonly _baseUrl; private readonly _fetch; private readonly _spawn; private readonly _exists; private readonly _linkFile; private readonly _copyFile; private readonly _envPath; private readonly _homeDir; private readonly _sleep; private readonly _platform; private readonly _arch; private readonly _createZstdDecompress; private readonly _hasCommand; private readonly _extractArchiveFn; private readonly _profile; private _child; private _childModelsDir; constructor(args: { agentDir: string; baseUrl?: string; profileMode?: LocalInferenceProfileMode; deps?: LocalRuntimeDeps; }); get baseUrl(): string; ownedModelsDir(): string; userModelsDir(): string; private _storeSummary; private _countStoreModels; private _walkFiles; private _findBinary; private _serverUp; private _serverModels; private _activeStoreSummary; detect(): Promise; importUserModels(): OllamaImportResult; private _importManifestFiles; private _importBlobFiles; /** * Manual-steps fallback text — shown when the managed install below wasn't offered (headless), * was declined, or itself failed. #31 reversed the stance for ollama specifically: pi CAN install * it for you now (consent-gated, a controlled download into pi's own runtimes dir, never * `curl | sh`) — these steps are the alternative for when that path isn't taken. */ installGuide(): string[]; /** Which decompressor to use for a `.tar.zst` asset — native (this Node's own zlib) is always * preferred when available, even if a system `zstd` ALSO exists, since it needs no external * dependency at all. Verified per-runtime by feature detection (see the constructor), not by * guessing from a Node version number. */ private _chooseZstdStrategy; /** * Managed install (#31): a consent-gated, controlled download of the pinned ollama release into * pi's OWN runtimes dir (`/runtimes/ollama/`) — never `curl | sh`, never a second * server instance, and distinct from {@link start}'s owned MODEL storage (this is the binary * itself). Callers (the router's consent flow, and later a doctor-driven install) own the * consent/UI; this method only does the mechanical download+extract+verify and reports the * outcome honestly. `onProgress` is best-effort UI feedback, not a completion signal. */ installManaged(onProgress?: (status: string) => void): Promise<{ ok: boolean; error?: string; }>; private _installArchive; /** Real extraction: `tar-gz` lets `tar` itself gunzip; `tar-zst` decompresses first (native * node:zlib preferred, system `zstd` as fallback, an honest error if neither exists — see * {@link _chooseZstdStrategy}); `zip` needs a seekable file, so it's buffered to disk first. */ private _extractArchive; private _extractTar; private _extractZip; /** Shared spawn-then-health-poll for both start modes below; `extraEnv` is the only thing that * differs between them (owned storage vs reusing the user's own). */ private _spawnAndPoll; private _startServer; /** * Start a pi-managed serve with OWNED storage and the hardened env verified on this class of * hardware. No-op (reported) when a server already responds — pi never double-serves. Used by * `/models add` et al, where isolated per-model-pull storage is the point. */ start(): Promise<{ started: boolean; reason: string; }>; /** Start against one caller-selected store; acceptance harnesses use this on an isolated port. */ startWithModelsStore(modelsDir: string): Promise<{ started: boolean; reason: string; }>; /** * Start a serve that REUSES the user's own existing models directory (no `OLLAMA_MODELS` * override — falls through to Ollama's own default, `~/.ollama`), for callers that must see the * user's already-pulled models rather than pi's isolated/owned storage. Same idempotency and * hardened perf env as {@link start}; the only difference is which storage the server sees. */ startReuseExisting(): Promise<{ started: boolean; reason: string; }>; /** Resource hygiene only: stops the pi-managed serve process; never deletes anything. */ stop(): { stopped: boolean; }; list(): Promise; listResidentModels(): Promise; ensureResident(model: string): Promise<{ ok: boolean; error?: string; }>; releaseResident(model: string): Promise<{ ok: boolean; error?: string; }>; private _postKeepAlive; show(ref: string): Promise<{ ok: true; info: OllamaModelInfo; } | { ok: false; error: string; }>; private _requestModelMutation; createModel(args: OllamaCreateModelInput): Promise<{ ok: true; } | { ok: false; error: string; }>; /** Pull a model through the server API (weights land in the SERVER's models dir). */ pull(ref: string, onProgress?: (status: string) => void): Promise<{ ok: boolean; error?: string; }>; /** EXPLICIT user action only — callers must have shown what gets deleted and confirmed. */ remove(ref: string): Promise<{ ok: boolean; error?: string; }>; } export interface TransformersRuntimeStatus { runtimeInstalled: boolean; serverUp: boolean; baseUrl: string; modelId: string; venvDir: string; cacheDir: string; serverScriptPath: string; } export declare class TransformersRuntime { private readonly _agentDir; private readonly _modelId; private readonly _baseUrl; private readonly _fetch; private readonly _spawn; private readonly _exists; private readonly _sleep; private readonly _platform; private readonly _runCommand; private readonly _serverScriptPath; private _proc?; constructor(args: { agentDir: string; modelId: string; baseUrl?: string; deps?: LocalRuntimeDeps; }); get baseUrl(): string; get modelId(): string; get runtimeDir(): string; get venvDir(): string; get cacheDir(): string; get pythonPath(): string; private get serverPort(); private get serverHost(); private runtimeEnv; private serverUp; detect(): Promise; private pythonCandidates; private findPython; private torchInstallArgs; private runPython; private runtimeDependenciesInstalled; private readPyvenvHome; private venvInterpreterCoherent; private venvHealthy; private linuxVenvPackageCommand; private venvInstallHint; private venvFailure; private createVenv; private repairExistingVenv; private ensurePipAvailable; installManaged(onProgress?: (status: string) => void): Promise<{ ok: boolean; error?: string; }>; downloadModel(onProgress?: (status: string) => void): Promise<{ ok: boolean; error?: string; }>; start(): Promise<{ started: boolean; reason: string; }>; stop(): { stopped: boolean; }; installGuide(): string[]; } //# sourceMappingURL=local-runtime.d.ts.map