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
183 lines
6.3 KiB
TypeScript
183 lines
6.3 KiB
TypeScript
/**
|
|
* Text-handler wiring tests.
|
|
*
|
|
* Confirms the unified local provider:
|
|
* - registers TEXT_SMALL + TEXT_LARGE handlers,
|
|
* - dispatches both onto the loader registered as "localInferenceLoader",
|
|
* - threads abort signals through to the loader,
|
|
* - returns a structured LocalInferenceUnavailableError when the runtime
|
|
* has no loader at all (so the runtime falls back to a non-local provider
|
|
* rather than silently serving a zero output).
|
|
*
|
|
* This is the runtime contract every platform path (Linux/macOS/Windows
|
|
* via capacitor-llama + llama-server, AOSP via plugin-aosp-local-inference,
|
|
* iOS/Android via capacitor-llama) MUST satisfy. The handlers live in
|
|
* provider.ts and look up the loader by name — so as long as a platform's
|
|
* loader registers under "localInferenceLoader" and implements
|
|
* `generate(args)`, the runtime's `useModel(TEXT_*, ...)` reaches the
|
|
* loaded model on every platform.
|
|
*/
|
|
import { ModelType } from "@elizaos/core";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
createLocalInferenceModelHandlers,
|
|
isLocalInferenceUnavailableError,
|
|
} from "../src/provider.ts";
|
|
|
|
function runtimeWithService(service: Record<string, unknown>) {
|
|
return {
|
|
getService: vi.fn((name: string) =>
|
|
name === "localInferenceLoader" ? service : null,
|
|
),
|
|
};
|
|
}
|
|
|
|
describe("provider TEXT_SMALL / TEXT_LARGE dispatch", () => {
|
|
it("registers both text handlers under the unified provider", () => {
|
|
const handlers = createLocalInferenceModelHandlers();
|
|
expect(typeof handlers[ModelType.TEXT_SMALL]).toBe("function");
|
|
expect(typeof handlers[ModelType.TEXT_LARGE]).toBe("function");
|
|
});
|
|
|
|
it("dispatches TEXT_SMALL through the registered loader", async () => {
|
|
const generate = vi.fn(async (args: { prompt: string }) => {
|
|
return `small:${args.prompt}`;
|
|
});
|
|
const handlers = createLocalInferenceModelHandlers();
|
|
const runtime = runtimeWithService({ generate });
|
|
|
|
const result = await handlers[ModelType.TEXT_SMALL]?.(runtime as never, {
|
|
prompt: "hi",
|
|
maxTokens: 32,
|
|
} as never);
|
|
|
|
expect(result).toBe("small:hi");
|
|
expect(generate).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("dispatches TEXT_LARGE through the same loader surface (one loader; the slot is opaque to the loader)", async () => {
|
|
const generate = vi.fn(async () => "large:ok");
|
|
const handlers = createLocalInferenceModelHandlers();
|
|
const runtime = runtimeWithService({ generate });
|
|
|
|
const result = await handlers[ModelType.TEXT_LARGE]?.(runtime as never, {
|
|
prompt: "hi",
|
|
} as never);
|
|
|
|
expect(result).toBe("large:ok");
|
|
expect(generate).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("threads an AbortSignal through to the loader", async () => {
|
|
const seen: { signal?: AbortSignal } = {};
|
|
const generate = vi.fn(async (args: { signal?: AbortSignal }) => {
|
|
seen.signal = args.signal;
|
|
return "ok";
|
|
});
|
|
const handlers = createLocalInferenceModelHandlers();
|
|
const runtime = runtimeWithService({ generate });
|
|
|
|
const controller = new AbortController();
|
|
await handlers[ModelType.TEXT_SMALL]?.(runtime as never, {
|
|
prompt: "hi",
|
|
signal: controller.signal,
|
|
} as never);
|
|
|
|
expect(seen.signal).toBe(controller.signal);
|
|
});
|
|
|
|
it("forwards stop sequences, temperature, and top-p verbatim", async () => {
|
|
const generate = vi.fn(async () => "ok");
|
|
const handlers = createLocalInferenceModelHandlers();
|
|
const runtime = runtimeWithService({ generate });
|
|
|
|
await handlers[ModelType.TEXT_LARGE]?.(runtime as never, {
|
|
prompt: "hi",
|
|
stopSequences: ["</done>"],
|
|
temperature: 0.1,
|
|
topP: 0.95,
|
|
maxTokens: 512,
|
|
} as never);
|
|
|
|
expect(generate).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
prompt: "hi",
|
|
stopSequences: ["</done>"],
|
|
temperature: 0.1,
|
|
topP: 0.95,
|
|
maxTokens: 512,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("emits a typed LOCAL_INFERENCE_UNAVAILABLE error when no loader is registered (runtime then falls through to next provider)", async () => {
|
|
const handlers = createLocalInferenceModelHandlers();
|
|
let caught: unknown;
|
|
try {
|
|
await handlers[ModelType.TEXT_SMALL]?.({} as never, {
|
|
prompt: "hi",
|
|
} as never);
|
|
} catch (err) {
|
|
caught = err;
|
|
}
|
|
expect(isLocalInferenceUnavailableError(caught)).toBe(true);
|
|
expect((caught as { reason?: string }).reason).toBe("backend_unavailable");
|
|
});
|
|
|
|
it("emits a typed LOCAL_INFERENCE_UNAVAILABLE error when the loader is registered without `generate` (capability_unavailable)", async () => {
|
|
const handlers = createLocalInferenceModelHandlers();
|
|
const runtime = runtimeWithService({});
|
|
let caught: unknown;
|
|
try {
|
|
await handlers[ModelType.TEXT_LARGE]?.(runtime as never, {
|
|
prompt: "hi",
|
|
} as never);
|
|
} catch (err) {
|
|
caught = err;
|
|
}
|
|
expect(isLocalInferenceUnavailableError(caught)).toBe(true);
|
|
expect((caught as { reason?: string }).reason).toBe(
|
|
"capability_unavailable",
|
|
);
|
|
});
|
|
|
|
it("rejects empty / whitespace-only prompts (invalid_input) — must not silently dispatch (Commandment 8)", async () => {
|
|
const generate = vi.fn(async () => "should-not-fire");
|
|
const handlers = createLocalInferenceModelHandlers();
|
|
const runtime = runtimeWithService({ generate });
|
|
|
|
await expect(
|
|
handlers[ModelType.TEXT_SMALL]?.(runtime as never, {
|
|
prompt: " ",
|
|
} as never),
|
|
).rejects.toMatchObject({
|
|
code: "LOCAL_INFERENCE_UNAVAILABLE",
|
|
reason: "invalid_input",
|
|
});
|
|
expect(generate).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("provider TEXT dispatch — arbiter accessor visibility", () => {
|
|
it("does NOT require an arbiter for text — text bypasses the arbiter capability queue and goes straight to the loader", async () => {
|
|
// MemoryArbiter ownership: text is registered with priority-100 in
|
|
// WS1 (see `memory-arbiter.ts` CAPABILITY_ROLE: text → "text-target").
|
|
// The provider's text path, however, calls `loader.generate()`
|
|
// directly — the arbiter is used by the *loader* internally, not by
|
|
// the provider. This test pins that contract: a loader without
|
|
// `getMemoryArbiter()` still serves text generation cleanly.
|
|
const generate = vi.fn(async () => "ok");
|
|
const handlers = createLocalInferenceModelHandlers();
|
|
const runtime = {
|
|
getService: vi.fn((name: string) =>
|
|
name === "localInferenceLoader" ? { generate } : null,
|
|
),
|
|
};
|
|
await expect(
|
|
handlers[ModelType.TEXT_SMALL]?.(runtime as never, {
|
|
prompt: "hi",
|
|
} as never),
|
|
).resolves.toBe("ok");
|
|
});
|
|
});
|