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

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");
});
});