Files
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

114 lines
4.5 KiB
TypeScript

/**
* Ensures a model exists on the Ollama server before inference: probes `/api/show` and, when the
* model is absent, issues a blocking `/api/pull`. Runs ahead of every text and embedding call
* (`models/text.ts`, `models/embedding.ts`), so a first-use miss adds download latency.
*
* ## Failure policy (#12796)
*
* Model discovery / auto-download is a local-inference bootstrap path: "not installed", "daemon
* unreachable", and "pull failed" are all distinct failures that MUST reach the caller — never a
* silent return that lets inference proceed against a model the daemon does not have. Swallowing
* the pull failure here previously converted a broken/absent model into a "healthy, proceed" state:
* the subsequent generate/embed call then failed deep in the AI SDK with an opaque error (or, for
* some daemon versions, produced a confusing 404), losing the actionable "download failed" signal.
*
* A successful `/api/show` (or a benign "model already present" 200) returns normally. Anything
* else throws a typed `OllamaModelUnavailableError` carrying the wire status so the outer text /
* embedding handler surfaces it to the model/caller (both call sites throw on error — see
* `models/text.ts` and `models/embedding.ts`).
*/
import { logger } from "@elizaos/core";
/**
* Raised when a model cannot be confirmed present on the Ollama daemon and cannot be pulled.
* Distinguishes the bootstrap-time failure from a downstream inference error so callers (and the
* agent) see "the local model is not available", not a generic completion failure.
*/
export class OllamaModelUnavailableError extends Error {
readonly reason: "daemon-unreachable" | "pull-failed";
readonly model: string;
readonly status?: number;
constructor(
message: string,
options: {
reason: "daemon-unreachable" | "pull-failed";
model: string;
status?: number;
cause?: unknown;
}
) {
super(message, options.cause !== undefined ? { cause: options.cause } : undefined);
this.name = "OllamaModelUnavailableError";
this.reason = options.reason;
this.model = options.model;
if (options.status !== undefined) {
this.status = options.status;
}
}
}
export async function ensureModelAvailable(
model: string,
providedBaseURL?: string,
customFetch?: typeof fetch | null
): Promise<void> {
const baseURL = providedBaseURL || "http://localhost:11434/api";
const apiBase = baseURL.endsWith("/api") ? baseURL.slice(0, -4) : baseURL;
const fetcher = customFetch ?? fetch;
let showRes: Response;
try {
showRes = await fetcher(`${apiBase}/api/show`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model }),
});
} catch (err) {
// The Ollama daemon is unreachable (connection refused / DNS / timeout). This is NOT
// "model absent" — we cannot even ask. Surface it so the caller reports the local
// inference backend as down rather than proceeding into an inference call that will
// fail with a less actionable error.
throw new OllamaModelUnavailableError(
`[Ollama] Cannot reach the Ollama daemon at ${apiBase} to verify model "${model}": ${
err instanceof Error ? err.message : String(err)
}`,
{ reason: "daemon-unreachable", model, cause: err }
);
}
if (showRes.ok) {
return;
}
logger.info(`[Ollama] Model ${model} not found locally. Downloading...`);
let pullRes: Response;
try {
pullRes = await fetcher(`${apiBase}/api/pull`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model, stream: false }),
});
} catch (err) {
throw new OllamaModelUnavailableError(
`[Ollama] Auto-download of model "${model}" failed to reach ${apiBase}/api/pull: ${
err instanceof Error ? err.message : String(err)
}`,
{ reason: "pull-failed", model, cause: err }
);
}
if (!pullRes.ok) {
// The pull request was answered but the daemon could not fetch the model (unknown model
// name, registry error, out-of-disk, ...). Do NOT continue: the model still is not present,
// so the following generate/embed would fail anyway — fail here with the actionable reason.
throw new OllamaModelUnavailableError(
`[Ollama] Failed to pull model "${model}": HTTP ${pullRes.status} ${pullRes.statusText}`,
{ reason: "pull-failed", model, status: pullRes.status }
);
}
logger.info(`[Ollama] Downloaded model ${model}`);
}