mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 10:55:31 -06:00
9320bd379e
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
2526 lines
79 KiB
TypeScript
2526 lines
79 KiB
TypeScript
import { expectDefined } from "@openclaw/normalization-core";
|
|
import type { ProviderAuthMethod } from "openclaw/plugin-sdk/plugin-entry";
|
|
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
|
|
import { clearLiveCatalogCacheForTests } from "openclaw/plugin-sdk/provider-catalog-shared";
|
|
// Ollama tests cover index plugin behavior.
|
|
import { createRequireRecord } from "openclaw/plugin-sdk/test-fixtures";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import plugin from "./index.js";
|
|
import { OLLAMA_DEFAULT_API_KEY } from "./src/discovery-shared.js";
|
|
|
|
const promptAndConfigureOllamaMock = vi.hoisted(() =>
|
|
vi.fn(async () => ({
|
|
credential: "ollama-local",
|
|
defaultModel: "ollama/qwen-tool",
|
|
config: {
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
baseUrl: "http://127.0.0.1:11434",
|
|
api: "ollama",
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})),
|
|
);
|
|
const ensureOllamaModelPulledMock = vi.hoisted(() => vi.fn(async () => {}));
|
|
const checkOllamaCloudAuthMock = vi.hoisted(() => vi.fn());
|
|
const configureOllamaNonInteractiveMock = vi.hoisted(() => vi.fn());
|
|
const fetchOllamaModelsMock = vi.hoisted(() => vi.fn());
|
|
const fetchLoadedOllamaModelNamesMock = vi.hoisted(() => vi.fn());
|
|
const buildOllamaProviderMock = vi.hoisted(() => vi.fn());
|
|
const queryOllamaModelShowInfoMock = vi.hoisted(() => vi.fn());
|
|
const resolveConfiguredSecretInputStringMock = vi.hoisted(() => vi.fn());
|
|
const buildOllamaModelDefinitionMock = vi.hoisted(() =>
|
|
vi.fn((modelId: string, contextWindow?: number, capabilities?: string[]) => {
|
|
const normalized = modelId.trim().toLowerCase();
|
|
const isKnownCloudReasoningModel =
|
|
normalized === "glm-5.2:cloud" || /^deepseek-v4-(?:flash|pro):cloud$/.test(normalized);
|
|
return {
|
|
id: modelId,
|
|
name: modelId,
|
|
reasoning: isKnownCloudReasoningModel || (capabilities?.includes("thinking") ?? false),
|
|
input: capabilities?.includes("vision") ? ["text", "image"] : ["text"],
|
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
contextWindow: contextWindow ?? (normalized === "glm-5.2:cloud" ? 1_000_000 : 8192),
|
|
maxTokens: 8192,
|
|
compat: capabilities
|
|
? { supportsTools: capabilities.includes("tools"), supportsUsageInStreaming: true }
|
|
: { supportsUsageInStreaming: true },
|
|
};
|
|
}),
|
|
);
|
|
const createConfiguredOllamaStreamFnMock = vi.hoisted(() =>
|
|
vi.fn((_params: { model: unknown; providerBaseUrl?: string }) => (() => ({})) as never),
|
|
);
|
|
|
|
vi.mock("./src/provider-models.js", async (importOriginal) => ({
|
|
...(await importOriginal<typeof import("./src/provider-models.js")>()),
|
|
buildOllamaModelDefinition: buildOllamaModelDefinitionMock,
|
|
buildOllamaProvider: buildOllamaProviderMock,
|
|
fetchOllamaModels: fetchOllamaModelsMock,
|
|
fetchLoadedOllamaModelNames: fetchLoadedOllamaModelNamesMock,
|
|
queryOllamaModelShowInfo: queryOllamaModelShowInfoMock,
|
|
}));
|
|
|
|
vi.mock("openclaw/plugin-sdk/secret-input-runtime", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/secret-input-runtime")>();
|
|
return {
|
|
...actual,
|
|
resolveConfiguredSecretInputString: resolveConfiguredSecretInputStringMock.mockImplementation(
|
|
actual.resolveConfiguredSecretInputString,
|
|
),
|
|
};
|
|
});
|
|
|
|
vi.mock("./src/setup.runtime.js", async (importOriginal) => ({
|
|
...(await importOriginal<typeof import("./src/setup.runtime.js")>()),
|
|
checkOllamaCloudAuth: checkOllamaCloudAuthMock,
|
|
configureOllamaNonInteractive: configureOllamaNonInteractiveMock,
|
|
ensureOllamaModelPulled: ensureOllamaModelPulledMock,
|
|
promptAndConfigureOllama: promptAndConfigureOllamaMock,
|
|
}));
|
|
|
|
vi.mock("./src/stream-registration.js", () => ({
|
|
createLazyConfiguredOllamaStreamFn: createConfiguredOllamaStreamFnMock,
|
|
}));
|
|
|
|
beforeEach(() => {
|
|
clearLiveCatalogCacheForTests();
|
|
promptAndConfigureOllamaMock.mockClear();
|
|
ensureOllamaModelPulledMock.mockClear();
|
|
checkOllamaCloudAuthMock.mockReset();
|
|
checkOllamaCloudAuthMock.mockResolvedValue({ signedIn: true });
|
|
configureOllamaNonInteractiveMock.mockReset();
|
|
fetchOllamaModelsMock.mockReset();
|
|
fetchLoadedOllamaModelNamesMock.mockReset();
|
|
fetchLoadedOllamaModelNamesMock.mockResolvedValue({
|
|
reachable: true,
|
|
models: ["qwen-tool", "qwen3.5:4b", "llama3.3:70b", "nomic-embed-text", "unknown-tools"],
|
|
});
|
|
buildOllamaProviderMock.mockReset();
|
|
queryOllamaModelShowInfoMock.mockReset();
|
|
resolveConfiguredSecretInputStringMock.mockClear();
|
|
queryOllamaModelShowInfoMock.mockResolvedValue({
|
|
contextWindow: 32_768,
|
|
capabilities: ["completion", "tools"],
|
|
});
|
|
buildOllamaModelDefinitionMock.mockClear();
|
|
createConfiguredOllamaStreamFnMock.mockClear();
|
|
});
|
|
|
|
function registerProvider() {
|
|
return registerProvidersWithPluginConfig({}).find((provider) => provider.id === "ollama");
|
|
}
|
|
|
|
function registerProvidersWithPluginConfig(pluginConfig: Record<string, unknown>) {
|
|
const registerProviderMock = vi.fn();
|
|
|
|
plugin.register(
|
|
createTestPluginApi({
|
|
id: "ollama",
|
|
name: "Ollama",
|
|
source: "test",
|
|
config: {},
|
|
pluginConfig,
|
|
runtime: {} as never,
|
|
registerProvider: registerProviderMock,
|
|
}),
|
|
);
|
|
|
|
expect(registerProviderMock).toHaveBeenCalledTimes(2);
|
|
return registerProviderMock.mock.calls.map((call) => call[0]);
|
|
}
|
|
|
|
function registerProviderWithPluginConfig(pluginConfig: Record<string, unknown>) {
|
|
return registerProvidersWithPluginConfig(pluginConfig).find(
|
|
(provider) => provider.id === "ollama",
|
|
);
|
|
}
|
|
|
|
function registerOllamaCloudProvider() {
|
|
return registerProvidersWithPluginConfig({}).find((provider) => provider.id === "ollama-cloud");
|
|
}
|
|
|
|
describe("ollama tool-schema compatibility", () => {
|
|
it("registers llama.cpp GBNF projection for local and cloud providers", () => {
|
|
for (const provider of registerProvidersWithPluginConfig({})) {
|
|
expect(provider).toMatchObject({
|
|
normalizeToolSchemas: expect.any(Function),
|
|
inspectToolSchemas: expect.any(Function),
|
|
});
|
|
}
|
|
});
|
|
|
|
it("keeps configured-row projection aligned with absent runtime model normalizers", () => {
|
|
for (const provider of registerProvidersWithPluginConfig({})) {
|
|
expect(provider.normalizeResolvedModel).toBeUndefined();
|
|
}
|
|
});
|
|
});
|
|
|
|
function createOllamaResetValidationContext(
|
|
opts: Record<string, unknown> = {},
|
|
): Parameters<NonNullable<ProviderAuthMethod["validateNonInteractive"]>>[0] {
|
|
return {
|
|
authChoice: "ollama",
|
|
config: {},
|
|
baseConfig: {},
|
|
opts,
|
|
runtime: {
|
|
log: vi.fn(),
|
|
error: vi.fn(),
|
|
exit: vi.fn() as never,
|
|
},
|
|
resolveApiKey: vi.fn(async () => null),
|
|
};
|
|
}
|
|
|
|
const requireRecord = createRequireRecord("object", "expected-label");
|
|
|
|
function requireConfiguredStreamParams(): Record<string, unknown> {
|
|
return requireRecord(createConfiguredOllamaStreamFnMock.mock.calls[0]?.[0], "stream params");
|
|
}
|
|
|
|
function mockDiscoveredOllamaProvider(
|
|
models: Array<Record<string, unknown>>,
|
|
options: { baseUrl?: string; once?: boolean } = {},
|
|
) {
|
|
const provider = {
|
|
baseUrl: options.baseUrl ?? "http://127.0.0.1:11434",
|
|
api: "ollama",
|
|
models,
|
|
};
|
|
if (options.once) {
|
|
buildOllamaProviderMock.mockResolvedValueOnce(provider);
|
|
} else {
|
|
buildOllamaProviderMock.mockResolvedValue(provider);
|
|
}
|
|
}
|
|
|
|
function mockOllamaShowInfo(
|
|
capabilities: string[],
|
|
options: { contextWindow?: number; once?: boolean } = {},
|
|
) {
|
|
const info = {
|
|
contextWindow: options.contextWindow ?? 1_048_576,
|
|
capabilities,
|
|
};
|
|
if (options.once === false) {
|
|
queryOllamaModelShowInfoMock.mockResolvedValue(info);
|
|
} else {
|
|
queryOllamaModelShowInfoMock.mockResolvedValueOnce(info);
|
|
}
|
|
}
|
|
|
|
function createDynamicModelContext(modelId: string, config: Record<string, unknown> = {}) {
|
|
return {
|
|
config,
|
|
provider: "ollama",
|
|
modelId,
|
|
modelRegistry: { find: vi.fn(() => null) },
|
|
};
|
|
}
|
|
|
|
async function augmentOllamaCatalog(
|
|
provider: ReturnType<typeof registerProvider>,
|
|
overrides: Record<string, unknown> = {},
|
|
) {
|
|
return await provider.augmentModelCatalog?.({
|
|
config: {},
|
|
env: process.env,
|
|
entries: [],
|
|
...overrides,
|
|
} as never);
|
|
}
|
|
|
|
function captureWrappedOllamaPayload(
|
|
thinkingLevel: "off" | "minimal" | "low" | "medium" | "high" | "max" | undefined,
|
|
route: { provider?: string; modelId?: string; baseUrl?: string } = {},
|
|
) {
|
|
const provider = registerProvider();
|
|
const providerId = route.provider ?? "ollama";
|
|
const modelId = route.modelId ?? "qwen3.5:9b";
|
|
const baseUrl = route.baseUrl ?? "http://127.0.0.1:11434";
|
|
let payloadSeen: Record<string, unknown> | undefined;
|
|
const baseStreamFn = vi.fn((_model, _context, options) => {
|
|
const payload: Record<string, unknown> = {
|
|
messages: [],
|
|
options: { num_ctx: 65536 },
|
|
stream: true,
|
|
};
|
|
options?.onPayload?.(payload, _model);
|
|
payloadSeen = payload;
|
|
return {} as never;
|
|
});
|
|
|
|
const wrapped = provider.wrapStreamFn?.({
|
|
config: {
|
|
models: {
|
|
providers: {
|
|
[providerId]: {
|
|
api: "ollama",
|
|
baseUrl,
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
provider: providerId,
|
|
modelId,
|
|
thinkingLevel,
|
|
model: {
|
|
api: "ollama",
|
|
provider: providerId,
|
|
id: modelId,
|
|
baseUrl,
|
|
contextWindow: 131_072,
|
|
},
|
|
streamFn: baseStreamFn,
|
|
});
|
|
|
|
if (!wrapped) {
|
|
throw new Error("expected Ollama thinking stream wrapper");
|
|
}
|
|
void wrapped(
|
|
{
|
|
api: "ollama",
|
|
provider: providerId,
|
|
id: modelId,
|
|
} as never,
|
|
{} as never,
|
|
{},
|
|
);
|
|
return { baseStreamFn, payloadSeen };
|
|
}
|
|
|
|
describe("ollama plugin", () => {
|
|
it.each([
|
|
{
|
|
name: "preflights an available local model before destructive non-interactive reset",
|
|
models: ["gemma4"],
|
|
customBaseUrl: "http://ollama-host:11434/",
|
|
customModelId: "gemma4",
|
|
},
|
|
{
|
|
name: "rejects an unreachable Ollama endpoint before destructive reset",
|
|
reachable: false,
|
|
models: [],
|
|
error:
|
|
"Ollama could not be reached at http://ollama-host:11434.\nDownload it at https://ollama.com/download",
|
|
},
|
|
{
|
|
name: "rejects a missing requested Ollama model even when another model is available",
|
|
models: ["qwen2.5-coder:7b"],
|
|
customModelId: "gemma4",
|
|
error:
|
|
"Ollama model gemma4 was not found at http://ollama-host:11434.\nAvailable models: qwen2.5-coder:7b",
|
|
},
|
|
{
|
|
name: "recognizes the implicit latest tag without pulling during reset preflight",
|
|
models: ["gemma4:latest"],
|
|
customModelId: "ollama/gemma4",
|
|
},
|
|
{
|
|
name: "preflights the canonical default Ollama model without pulling it",
|
|
models: ["gemma4:latest"],
|
|
},
|
|
{
|
|
name: "rejects an unavailable default Ollama model before destructive reset",
|
|
models: ["qwen2.5-coder:7b"],
|
|
error:
|
|
"Ollama model gemma4 was not found at http://ollama-host:11434.\nAvailable models: qwen2.5-coder:7b",
|
|
},
|
|
{
|
|
name: "refuses to pull an unavailable local model during destructive-reset preflight",
|
|
models: [],
|
|
customModelId: "gemma4",
|
|
error:
|
|
"No Ollama models are available at http://ollama-host:11434.\nPull a model first, then re-run setup.",
|
|
},
|
|
{
|
|
name: "preflights an authenticated and confirmed cloud model without pulling it",
|
|
models: [],
|
|
customModelId: "ollama/kimi-k2.5:cloud",
|
|
cloud: "confirmed",
|
|
},
|
|
{
|
|
name: "rejects an unauthenticated Ollama cloud model before destructive reset",
|
|
models: [],
|
|
customModelId: "kimi-k2.5:cloud",
|
|
cloud: "unauthenticated",
|
|
error: "Cloud models on this Ollama host need `ollama signin`.\nhttps://ollama.com/signin",
|
|
},
|
|
{
|
|
name: "rejects an unconfirmed Ollama cloud model before destructive reset",
|
|
models: [],
|
|
customModelId: "kimi-k2.5:cloud",
|
|
cloud: "unconfirmed",
|
|
error:
|
|
"Ollama model kimi-k2.5:cloud was not found at http://ollama-host:11434.\nAvailable models: (none)",
|
|
},
|
|
{
|
|
name: "confirms a catalog-listed Ollama cloud model before destructive reset",
|
|
models: ["kimi-k2.5:cloud"],
|
|
customModelId: "kimi-k2.5:cloud",
|
|
cloud: "confirmed",
|
|
},
|
|
{
|
|
name: "rejects a stale catalog-listed Ollama cloud model before destructive reset",
|
|
models: ["kimi-k2.5:cloud"],
|
|
customModelId: "kimi-k2.5:cloud",
|
|
cloud: "unconfirmed",
|
|
error:
|
|
"Ollama model kimi-k2.5:cloud was not found at http://ollama-host:11434.\nAvailable models: kimi-k2.5:cloud",
|
|
},
|
|
] as Array<{
|
|
name: string;
|
|
models: string[];
|
|
reachable?: boolean;
|
|
customBaseUrl?: string;
|
|
customModelId?: string;
|
|
cloud?: "confirmed" | "unauthenticated" | "unconfirmed";
|
|
error?: string;
|
|
}>)("$name", async ({ models, reachable = true, customBaseUrl, customModelId, cloud, error }) => {
|
|
fetchOllamaModelsMock.mockResolvedValue({
|
|
reachable,
|
|
models: models.map((name) => ({ name })),
|
|
});
|
|
if (cloud === "unauthenticated") {
|
|
checkOllamaCloudAuthMock.mockResolvedValue({
|
|
signedIn: false,
|
|
signinUrl: "https://ollama.com/signin",
|
|
});
|
|
}
|
|
if (cloud === "unconfirmed") {
|
|
queryOllamaModelShowInfoMock.mockResolvedValue({});
|
|
}
|
|
|
|
const ctx = createOllamaResetValidationContext({
|
|
customBaseUrl: customBaseUrl ?? "http://ollama-host:11434",
|
|
...(customModelId ? { customModelId } : {}),
|
|
});
|
|
const validate = registerProvider().auth[0].validateNonInteractive;
|
|
expect(validate).toBeTypeOf("function");
|
|
await expect(validate(ctx)).resolves.toBe(!error);
|
|
|
|
if (customBaseUrl?.endsWith("/")) {
|
|
expect(fetchOllamaModelsMock).toHaveBeenCalledWith("http://ollama-host:11434");
|
|
}
|
|
if (cloud === "confirmed") {
|
|
expect(checkOllamaCloudAuthMock).toHaveBeenCalledWith("http://ollama-host:11434");
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(
|
|
"http://ollama-host:11434",
|
|
"kimi-k2.5:cloud",
|
|
);
|
|
}
|
|
if (cloud === "unauthenticated") {
|
|
expect(queryOllamaModelShowInfoMock).not.toHaveBeenCalled();
|
|
}
|
|
if (error) {
|
|
expect(ctx.runtime.error).toHaveBeenCalledWith(error);
|
|
expect(ctx.runtime.exit).toHaveBeenCalledWith(1);
|
|
} else {
|
|
expect(ctx.runtime.exit).not.toHaveBeenCalled();
|
|
}
|
|
expect(configureOllamaNonInteractiveMock).not.toHaveBeenCalled();
|
|
expect(ensureOllamaModelPulledMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it.each(["ollama", "ollama-cloud"])(
|
|
"classifies incomplete %s streams as provider failures",
|
|
(providerId) => {
|
|
const provider = registerProvidersWithPluginConfig({}).find(
|
|
(candidate) => candidate.id === providerId,
|
|
);
|
|
|
|
expect(
|
|
provider?.classifyFailoverReason?.({
|
|
provider: providerId,
|
|
errorMessage: "Ollama API stream ended without a final response",
|
|
}),
|
|
).toBe("server_error");
|
|
expect(
|
|
provider?.classifyFailoverReason?.({
|
|
provider: providerId,
|
|
errorMessage: "Ollama returned malformed tool arguments",
|
|
}),
|
|
).toBeUndefined();
|
|
},
|
|
);
|
|
|
|
it("registers node-local inference commands, policy, and agent tool", () => {
|
|
const registerNodeHostCommand = vi.fn();
|
|
const registerNodeInvokePolicy = vi.fn();
|
|
const registerTool = vi.fn();
|
|
|
|
plugin.register(
|
|
createTestPluginApi({
|
|
id: "ollama",
|
|
name: "Ollama",
|
|
source: "test",
|
|
registerNodeHostCommand,
|
|
registerNodeInvokePolicy,
|
|
registerTool,
|
|
}),
|
|
);
|
|
|
|
expect(registerNodeHostCommand.mock.calls.map(([entry]) => entry.command)).toEqual([
|
|
"ollama.models",
|
|
"ollama.chat",
|
|
]);
|
|
expect(registerNodeInvokePolicy).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
commands: ["ollama.models", "ollama.chat"],
|
|
defaultPlatforms: ["macos", "linux", "windows"],
|
|
}),
|
|
);
|
|
expect(registerTool).toHaveBeenCalledWith(expect.objectContaining({ name: "node_inference" }));
|
|
});
|
|
|
|
it("keeps the agent tool but does not advertise node inference when disabled locally", () => {
|
|
const registerNodeHostCommand = vi.fn();
|
|
const registerNodeInvokePolicy = vi.fn();
|
|
const registerTool = vi.fn();
|
|
|
|
plugin.register(
|
|
createTestPluginApi({
|
|
id: "ollama",
|
|
name: "Ollama",
|
|
source: "test",
|
|
pluginConfig: { nodeInference: { enabled: false } },
|
|
registerNodeHostCommand,
|
|
registerNodeInvokePolicy,
|
|
registerTool,
|
|
}),
|
|
);
|
|
|
|
expect(registerNodeHostCommand).not.toHaveBeenCalled();
|
|
expect(registerNodeInvokePolicy).toHaveBeenCalledOnce();
|
|
expect(registerTool).toHaveBeenCalledWith(expect.objectContaining({ name: "node_inference" }));
|
|
});
|
|
|
|
it("returns the exact model selected during provider auth setup", async () => {
|
|
const provider = registerProvider();
|
|
|
|
const result = await provider.auth[0].run({
|
|
config: {},
|
|
prompter: {} as never,
|
|
isRemote: false,
|
|
openUrl: vi.fn(async () => undefined),
|
|
});
|
|
|
|
expect(promptAndConfigureOllamaMock).toHaveBeenCalledWith({
|
|
cfg: {},
|
|
env: undefined,
|
|
opts: undefined,
|
|
prompter: {},
|
|
secretInputMode: undefined,
|
|
allowSecretRefPrompt: undefined,
|
|
});
|
|
expect(result.configPatch).toEqual({
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
baseUrl: "http://127.0.0.1:11434",
|
|
api: "ollama",
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
expect(result.defaultModel).toBe("ollama/qwen-tool");
|
|
});
|
|
|
|
it("discovers and prepares a loaded tool-capable model without pulling it", async () => {
|
|
const provider = registerProvider();
|
|
const guided = provider.auth[0].appGuidedSetup;
|
|
mockDiscoveredOllamaProvider([
|
|
{ id: "embed-only", name: "embed-only", compat: { supportsTools: false } },
|
|
{ id: "unknown-tools", name: "unknown-tools" },
|
|
{ id: "qwen-tool", name: "qwen-tool", compat: { supportsTools: true } },
|
|
]);
|
|
|
|
await expect(guided?.detect({ config: {}, env: {} })).resolves.toEqual({
|
|
modelRef: "ollama/qwen-tool",
|
|
detail: "qwen-tool at http://127.0.0.1:11434",
|
|
});
|
|
const prepared = await guided?.prepare({
|
|
config: {},
|
|
env: {},
|
|
modelRef: "ollama/qwen-tool",
|
|
});
|
|
expect(prepared).toMatchObject({
|
|
profiles: [],
|
|
defaultModel: "ollama/qwen-tool",
|
|
configPatch: {
|
|
models: {
|
|
mode: "merge",
|
|
providers: {
|
|
ollama: {
|
|
baseUrl: "http://127.0.0.1:11434",
|
|
api: "ollama",
|
|
models: [
|
|
expect.objectContaining({ id: "embed-only" }),
|
|
expect.objectContaining({ id: "unknown-tools" }),
|
|
expect.objectContaining({ id: "qwen-tool" }),
|
|
],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
expect(prepared?.configPatch?.models?.providers?.ollama?.apiKey).toBe(OLLAMA_DEFAULT_API_KEY);
|
|
await expect(
|
|
guided?.prepare({ config: {}, env: {}, modelRef: "ollama/unknown-tools" }),
|
|
).resolves.toBeNull();
|
|
expect(ensureOllamaModelPulledMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("detects a reachable Ollama service without requiring a suitable model", async () => {
|
|
const provider = registerProvider();
|
|
fetchOllamaModelsMock.mockResolvedValue({ reachable: true, models: [] });
|
|
|
|
await expect(
|
|
provider.auth[0].appGuidedSetup?.detectAvailability?.({ config: {}, env: {} }),
|
|
).resolves.toBe(true);
|
|
expect(fetchOllamaModelsMock).toHaveBeenCalledWith("http://127.0.0.1:11434", {});
|
|
});
|
|
|
|
it("does not mark an unreachable Ollama service as available", async () => {
|
|
const provider = registerProvider();
|
|
fetchOllamaModelsMock.mockResolvedValue({ reachable: false, models: [] });
|
|
|
|
await expect(
|
|
provider.auth[0].appGuidedSetup?.detectAvailability?.({ config: {}, env: {} }),
|
|
).resolves.toBe(false);
|
|
});
|
|
|
|
it("uses the Docker host default for availability detection during Docker setup", async () => {
|
|
const provider = registerProvider();
|
|
fetchOllamaModelsMock.mockResolvedValue({ reachable: true, models: [] });
|
|
|
|
await provider.auth[0].appGuidedSetup?.detectAvailability?.({
|
|
config: {},
|
|
env: { OPENCLAW_DOCKER_SETUP: "1" },
|
|
});
|
|
|
|
expect(fetchOllamaModelsMock).toHaveBeenCalledWith("http://host.docker.internal:11434", {});
|
|
});
|
|
|
|
it("does not auto-detect installed models that are not loaded", async () => {
|
|
const provider = registerProvider();
|
|
fetchLoadedOllamaModelNamesMock.mockResolvedValue({ reachable: true, models: [] });
|
|
|
|
await expect(
|
|
provider.auth[0].appGuidedSetup?.detect({ config: {}, env: {} }),
|
|
).resolves.toBeNull();
|
|
|
|
expect(buildOllamaProviderMock).not.toHaveBeenCalled();
|
|
expect(queryOllamaModelShowInfoMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("prepares the exact configured model even when it is installed but idle", async () => {
|
|
const provider = registerProvider();
|
|
const config = {
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
baseUrl: "http://127.0.0.1:11434",
|
|
api: "ollama" as const,
|
|
models: [
|
|
{
|
|
id: "qwen-tool",
|
|
name: "qwen-tool",
|
|
reasoning: false,
|
|
input: ["text"] as const,
|
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
contextWindow: 32_768,
|
|
maxTokens: 8_192,
|
|
compat: { supportsTools: true },
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
fetchLoadedOllamaModelNamesMock.mockResolvedValue({ reachable: true, models: [] });
|
|
mockDiscoveredOllamaProvider([
|
|
{ id: "qwen-tool", name: "qwen-tool", compat: { supportsTools: true } },
|
|
]);
|
|
|
|
await expect(
|
|
provider.auth[0].appGuidedSetup?.prepare({
|
|
config,
|
|
env: {},
|
|
modelRef: "ollama/qwen-tool",
|
|
}),
|
|
).resolves.toMatchObject({
|
|
defaultModel: "ollama/qwen-tool",
|
|
configPatch: {
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
models: [expect.objectContaining({ id: "qwen-tool" })],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
expect(fetchLoadedOllamaModelNamesMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("rejects an explicit installed model that setup did not configure", async () => {
|
|
const provider = registerProvider();
|
|
mockDiscoveredOllamaProvider([
|
|
{ id: "other-model", name: "other-model", compat: { supportsTools: true } },
|
|
]);
|
|
|
|
await expect(
|
|
provider.auth[0].appGuidedSetup?.prepare({
|
|
config: {},
|
|
env: {},
|
|
modelRef: "ollama/other-model",
|
|
}),
|
|
).resolves.toBeNull();
|
|
expect(queryOllamaModelShowInfoMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("selects only from loaded models when stronger installed models are idle", async () => {
|
|
const provider = registerProvider();
|
|
fetchLoadedOllamaModelNamesMock.mockResolvedValue({
|
|
reachable: true,
|
|
models: ["llama3.3:70b"],
|
|
});
|
|
mockDiscoveredOllamaProvider([
|
|
{ id: "llama3.3:70b", name: "llama3.3:70b", compat: { supportsTools: true } },
|
|
{ id: "qwen3.5:4b", name: "qwen3.5:4b", compat: { supportsTools: true } },
|
|
]);
|
|
|
|
await expect(provider.auth[0].appGuidedSetup?.detect({ config: {}, env: {} })).resolves.toEqual(
|
|
{
|
|
modelRef: "ollama/llama3.3:70b",
|
|
detail: "llama3.3:70b at http://127.0.0.1:11434",
|
|
},
|
|
);
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledTimes(1);
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(
|
|
"http://127.0.0.1:11434",
|
|
"llama3.3:70b",
|
|
undefined,
|
|
);
|
|
});
|
|
|
|
it("rechecks loaded state before preparing the detected route", async () => {
|
|
const provider = registerProvider();
|
|
mockDiscoveredOllamaProvider([
|
|
{ id: "qwen-tool", name: "qwen-tool", compat: { supportsTools: true } },
|
|
]);
|
|
|
|
await expect(provider.auth[0].appGuidedSetup?.detect({ config: {}, env: {} })).resolves.toEqual(
|
|
{
|
|
modelRef: "ollama/qwen-tool",
|
|
detail: "qwen-tool at http://127.0.0.1:11434",
|
|
},
|
|
);
|
|
fetchLoadedOllamaModelNamesMock.mockResolvedValue({ reachable: true, models: [] });
|
|
|
|
await expect(
|
|
provider.auth[0].appGuidedSetup?.prepare({
|
|
config: {},
|
|
env: {},
|
|
modelRef: "ollama/qwen-tool",
|
|
}),
|
|
).resolves.toBeNull();
|
|
expect(buildOllamaProviderMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("prefers the strongest tool-calling family among loaded models", async () => {
|
|
const provider = registerProvider();
|
|
mockDiscoveredOllamaProvider([
|
|
{ id: "llama3.3:70b", name: "llama3.3:70b", compat: { supportsTools: true } },
|
|
{ id: "qwen3.5:4b", name: "qwen3.5:4b", compat: { supportsTools: true } },
|
|
{
|
|
id: "nomic-embed-text",
|
|
name: "nomic-embed-text",
|
|
compat: { supportsTools: true },
|
|
},
|
|
]);
|
|
|
|
await expect(provider.auth[0].appGuidedSetup?.detect({ config: {}, env: {} })).resolves.toEqual(
|
|
{
|
|
modelRef: "ollama/qwen3.5:4b",
|
|
detail: "qwen3.5:4b at http://127.0.0.1:11434",
|
|
},
|
|
);
|
|
});
|
|
|
|
it("skips preferred models whose measured context is below 16k", async () => {
|
|
const provider = registerProvider();
|
|
mockDiscoveredOllamaProvider([
|
|
{ id: "llama3.3:70b", name: "llama3.3:70b", compat: { supportsTools: true } },
|
|
{ id: "qwen3.5:4b", name: "qwen3.5:4b", compat: { supportsTools: true } },
|
|
]);
|
|
queryOllamaModelShowInfoMock.mockImplementation(async (_baseUrl: string, modelId: string) => ({
|
|
contextWindow: modelId === "qwen3.5:4b" ? 8_192 : 16_384,
|
|
capabilities: ["completion", "tools"],
|
|
}));
|
|
|
|
await expect(provider.auth[0].appGuidedSetup?.detect({ config: {}, env: {} })).resolves.toEqual(
|
|
{
|
|
modelRef: "ollama/llama3.3:70b",
|
|
detail: "llama3.3:70b at http://127.0.0.1:11434",
|
|
},
|
|
);
|
|
});
|
|
|
|
it("does not auto-detect a model without measured context metadata", async () => {
|
|
const provider = registerProvider();
|
|
mockDiscoveredOllamaProvider([
|
|
{ id: "qwen3.5:4b", name: "qwen3.5:4b", compat: { supportsTools: true } },
|
|
]);
|
|
queryOllamaModelShowInfoMock.mockResolvedValue({
|
|
capabilities: ["completion", "tools"],
|
|
});
|
|
|
|
await expect(
|
|
provider.auth[0].appGuidedSetup?.detect({ config: {}, env: {} }),
|
|
).resolves.toBeNull();
|
|
});
|
|
|
|
it("uses configured Ollama access while discovering installed models", async () => {
|
|
const provider = registerProvider();
|
|
const configuredValue = "configured-access";
|
|
const providerAccess = { apiKey: configuredValue };
|
|
mockDiscoveredOllamaProvider(
|
|
[{ id: "qwen-tool", name: "qwen-tool", compat: { supportsTools: true } }],
|
|
{ baseUrl: "https://ollama.example.com" },
|
|
);
|
|
|
|
await provider.auth[0].appGuidedSetup?.detect({
|
|
config: {
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
...providerAccess,
|
|
baseUrl: "https://ollama.example.com",
|
|
api: "ollama",
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: {},
|
|
});
|
|
|
|
expect(buildOllamaProviderMock).toHaveBeenCalledWith(
|
|
"https://ollama.example.com",
|
|
expect.objectContaining(providerAccess),
|
|
);
|
|
expect(fetchLoadedOllamaModelNamesMock).toHaveBeenCalledWith(
|
|
"https://ollama.example.com",
|
|
providerAccess,
|
|
);
|
|
});
|
|
|
|
it("keeps environment-backed Ollama access for the completion proposal", async () => {
|
|
const provider = registerProvider();
|
|
const configuredValue = "environment-access";
|
|
const providerAccess = { apiKey: configuredValue };
|
|
const environment = { OLLAMA_API_KEY: configuredValue };
|
|
mockDiscoveredOllamaProvider(
|
|
[{ id: "qwen-tool", name: "qwen-tool", compat: { supportsTools: true } }],
|
|
{ baseUrl: "https://ollama.example.com" },
|
|
);
|
|
|
|
const prepared = await provider.auth[0].appGuidedSetup?.prepare({
|
|
config: {
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
baseUrl: "https://ollama.example.com",
|
|
api: "ollama",
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: environment,
|
|
modelRef: "ollama/qwen-tool",
|
|
});
|
|
|
|
expect(buildOllamaProviderMock).toHaveBeenCalledWith(
|
|
"https://ollama.example.com",
|
|
expect.objectContaining(providerAccess),
|
|
);
|
|
expect(prepared?.configPatch?.models?.providers?.ollama?.apiKey).toBe("OLLAMA_API_KEY");
|
|
});
|
|
|
|
it("does not send the ambient Ollama cloud key to automatic localhost discovery", async () => {
|
|
const provider = registerProvider();
|
|
const configuredValue = "cloud-access";
|
|
const environment = { OLLAMA_API_KEY: configuredValue };
|
|
mockDiscoveredOllamaProvider([
|
|
{ id: "qwen-tool", name: "qwen-tool", compat: { supportsTools: true } },
|
|
]);
|
|
|
|
await provider.auth[0].appGuidedSetup?.detect({ config: {}, env: environment });
|
|
|
|
const options = buildOllamaProviderMock.mock.calls.at(-1)?.[1] as
|
|
| { apiKey?: string; quiet?: boolean }
|
|
| undefined;
|
|
expect(options?.apiKey).toBeUndefined();
|
|
expect(fetchLoadedOllamaModelNamesMock).toHaveBeenCalledWith("http://127.0.0.1:11434", {});
|
|
});
|
|
|
|
it("honors the Ollama discovery opt-out during app-guided detection", async () => {
|
|
const provider = registerProvider();
|
|
const context = {
|
|
config: {
|
|
plugins: { entries: { ollama: { config: { discovery: { enabled: false } } } } },
|
|
},
|
|
env: {},
|
|
};
|
|
|
|
await expect(provider.auth[0].appGuidedSetup?.detect(context)).resolves.toBeNull();
|
|
await expect(provider.auth[0].appGuidedSetup?.detectAvailability?.(context)).resolves.toBe(
|
|
false,
|
|
);
|
|
expect(fetchLoadedOllamaModelNamesMock).not.toHaveBeenCalled();
|
|
expect(buildOllamaProviderMock).not.toHaveBeenCalled();
|
|
expect(fetchOllamaModelsMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("pulls the model the user actually selected", async () => {
|
|
const provider = registerProvider();
|
|
const config = {
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
baseUrl: "http://127.0.0.1:11434",
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
const prompter = {} as never;
|
|
|
|
await provider.onModelSelected?.({
|
|
config,
|
|
model: "ollama/gemma4",
|
|
prompter,
|
|
});
|
|
|
|
expect(ensureOllamaModelPulledMock).toHaveBeenCalledWith({
|
|
config,
|
|
model: "ollama/gemma4",
|
|
prompter,
|
|
});
|
|
});
|
|
|
|
it("skips ambient discovery when plugin discovery is disabled", async () => {
|
|
const provider = registerProviderWithPluginConfig({ discovery: { enabled: false } });
|
|
|
|
const result = await provider.catalog.run({
|
|
config: {
|
|
plugins: {
|
|
entries: {
|
|
ollama: {
|
|
config: {
|
|
discovery: { enabled: false },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: {},
|
|
resolveProviderApiKey: () => ({ apiKey: "", discoveryApiKey: "" }),
|
|
} as never);
|
|
|
|
expect(result).toBeNull();
|
|
expect(buildOllamaProviderMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("uses live plugin config to re-enable discovery after startup disable", async () => {
|
|
const provider = registerProviderWithPluginConfig({ discovery: { enabled: false } });
|
|
mockDiscoveredOllamaProvider([{ id: "llama3.2", name: "Llama 3.2" }], { once: true });
|
|
|
|
const result = await provider.catalog.run({
|
|
config: {
|
|
plugins: {
|
|
entries: {
|
|
ollama: {
|
|
config: {
|
|
discovery: { enabled: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: { OLLAMA_API_KEY: "ollama-live" },
|
|
resolveProviderApiKey: () => ({ apiKey: "ollama-live", discoveryApiKey: "ollama-live" }),
|
|
} as never);
|
|
|
|
expect(buildOllamaProviderMock).toHaveBeenCalledOnce();
|
|
expect(result).toEqual({
|
|
provider: {
|
|
baseUrl: "http://127.0.0.1:11434",
|
|
api: "ollama",
|
|
models: [{ id: "llama3.2", name: "Llama 3.2" }],
|
|
apiKey: "ollama-local",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("skips ambient discovery without Ollama auth or meaningful config", async () => {
|
|
const provider = registerProvider();
|
|
|
|
const result = await provider.catalog.run({
|
|
config: {},
|
|
env: { NODE_ENV: "development" },
|
|
resolveProviderApiKey: () => ({ apiKey: "" }),
|
|
} as never);
|
|
|
|
expect(result).toBeNull();
|
|
expect(buildOllamaProviderMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("skips empty default-ish provider stubs without probing localhost", async () => {
|
|
const provider = registerProvider();
|
|
mockDiscoveredOllamaProvider([], { once: true });
|
|
|
|
const result = await provider.catalog.run({
|
|
config: {
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
baseUrl: "http://127.0.0.1:11434",
|
|
api: "ollama",
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: { NODE_ENV: "development" },
|
|
resolveProviderApiKey: () => ({ apiKey: "" }),
|
|
} as never);
|
|
|
|
expect(result).toBeNull();
|
|
expect(buildOllamaProviderMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it.each([
|
|
{ name: "treats non-default baseUrl as explicit discovery config", key: "baseUrl" },
|
|
{ name: "accepts baseURL alias as explicit discovery config", key: "baseURL" },
|
|
])("$name", async ({ key }) => {
|
|
const provider = registerProvider();
|
|
mockDiscoveredOllamaProvider([], { baseUrl: "http://remote-ollama:11434", once: true });
|
|
|
|
const result = await provider.catalog.run({
|
|
config: {
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
[key]: "http://remote-ollama:11434",
|
|
api: "ollama",
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: { NODE_ENV: "development" },
|
|
resolveProviderApiKey: () => ({ apiKey: "" }),
|
|
} as never);
|
|
|
|
expect(result).toBeNull();
|
|
expect(buildOllamaProviderMock).toHaveBeenCalledWith("http://remote-ollama:11434", {
|
|
quiet: false,
|
|
});
|
|
});
|
|
|
|
it("keeps stored ollama-local marker auth on the quiet ambient path", async () => {
|
|
const provider = registerProvider();
|
|
mockDiscoveredOllamaProvider([], { once: true });
|
|
|
|
const result = await provider.catalog.run({
|
|
config: {},
|
|
env: { NODE_ENV: "development" },
|
|
resolveProviderApiKey: () => ({ apiKey: "ollama-local" }),
|
|
} as never);
|
|
|
|
const resultProvider = requireRecord(result?.provider, "catalog provider");
|
|
expect(resultProvider.baseUrl).toBe("http://127.0.0.1:11434");
|
|
expect(resultProvider.api).toBe("ollama");
|
|
expect(resultProvider.apiKey).toBe("ollama-local");
|
|
expect(resultProvider.models).toEqual([]);
|
|
expect(buildOllamaProviderMock).toHaveBeenCalledWith(undefined, {
|
|
quiet: true,
|
|
});
|
|
});
|
|
|
|
it("resolves dynamic local models from Ollama without generating static models.json", async () => {
|
|
const provider = registerProvider();
|
|
const previous = process.env.OLLAMA_API_KEY;
|
|
process.env.OLLAMA_API_KEY = "ollama-local";
|
|
mockDiscoveredOllamaProvider(
|
|
[
|
|
{
|
|
id: "llama3.2:latest",
|
|
name: "llama3.2:latest",
|
|
reasoning: false,
|
|
input: ["text"],
|
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
contextWindow: 8192,
|
|
maxTokens: 2048,
|
|
},
|
|
],
|
|
{ once: true },
|
|
);
|
|
const context = createDynamicModelContext("llama3.2:latest");
|
|
|
|
try {
|
|
await provider.prepareDynamicModel?.(context as never);
|
|
|
|
const resolved = provider.resolveDynamicModel?.(context as never);
|
|
expect(resolved?.provider).toBe("ollama");
|
|
expect(resolved?.id).toBe("llama3.2:latest");
|
|
expect(resolved?.api).toBe("ollama");
|
|
expect(resolved?.baseUrl).toBe("http://127.0.0.1:11434");
|
|
expect(buildOllamaProviderMock).toHaveBeenCalledWith(undefined, { quiet: true });
|
|
} finally {
|
|
if (previous === undefined) {
|
|
delete process.env.OLLAMA_API_KEY;
|
|
} else {
|
|
process.env.OLLAMA_API_KEY = previous;
|
|
}
|
|
}
|
|
});
|
|
|
|
it("preserves explicit api for configured dynamic Ollama models", async () => {
|
|
const provider = registerProvider();
|
|
const previous = process.env.OLLAMA_API_KEY;
|
|
process.env.OLLAMA_API_KEY = "ollama-live";
|
|
mockDiscoveredOllamaProvider(
|
|
[
|
|
{
|
|
id: "qwen3-coder:cloud",
|
|
name: "qwen3-coder:cloud",
|
|
reasoning: false,
|
|
input: ["text"],
|
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
contextWindow: 8192,
|
|
maxTokens: 2048,
|
|
},
|
|
],
|
|
{ baseUrl: "https://ollama.example.com", once: true },
|
|
);
|
|
|
|
try {
|
|
const config = {
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
baseUrl: "https://ollama.example.com/v1",
|
|
api: "openai-completions",
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
const context = createDynamicModelContext("qwen3-coder:cloud", config);
|
|
|
|
await provider.prepareDynamicModel?.(context as never);
|
|
|
|
const resolved = provider.resolveDynamicModel?.(context as never);
|
|
expect(resolved?.provider).toBe("ollama");
|
|
expect(resolved?.id).toBe("qwen3-coder:cloud");
|
|
expect(resolved?.api).toBe("openai-completions");
|
|
expect(resolved?.baseUrl).toBe("https://ollama.example.com/v1");
|
|
expect(buildOllamaProviderMock).toHaveBeenCalledWith("https://ollama.example.com/v1", {
|
|
quiet: true,
|
|
apiKey: "ollama-live",
|
|
});
|
|
} finally {
|
|
if (previous === undefined) {
|
|
delete process.env.OLLAMA_API_KEY;
|
|
} else {
|
|
process.env.OLLAMA_API_KEY = previous;
|
|
}
|
|
}
|
|
});
|
|
|
|
it("authenticates configured dynamic Ollama discovery and model probes", async () => {
|
|
const provider = registerProvider();
|
|
const baseUrl = "https://dynamic-ollama.example.com";
|
|
const config = {
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
baseUrl,
|
|
api: "ollama" as const,
|
|
apiKey: "dynamic-discovery-access",
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
mockDiscoveredOllamaProvider([], { baseUrl, once: true });
|
|
const context = createDynamicModelContext("private-dynamic-model", config);
|
|
|
|
await provider.prepareDynamicModel?.(context as never);
|
|
|
|
expect(buildOllamaProviderMock).toHaveBeenCalledWith(baseUrl, {
|
|
quiet: true,
|
|
apiKey: "dynamic-discovery-access",
|
|
});
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(baseUrl, "private-dynamic-model", {
|
|
apiKey: "dynamic-discovery-access",
|
|
});
|
|
expect(provider.resolveDynamicModel?.(context as never)?.id).toBe("private-dynamic-model");
|
|
});
|
|
|
|
it("scopes dynamic Ollama model caches to the effective credential", async () => {
|
|
const provider = registerProvider();
|
|
const baseUrl = "https://shared-dynamic-ollama.example.com";
|
|
const modelId = "tenant-dynamic-model";
|
|
const configFor = (apiKey: string) => ({
|
|
models: {
|
|
providers: {
|
|
ollama: { baseUrl, api: "ollama" as const, apiKey, models: [] },
|
|
},
|
|
},
|
|
});
|
|
const discoveredFor = (name: string) => ({
|
|
baseUrl,
|
|
api: "ollama",
|
|
models: [{ id: modelId, name, contextWindow: 8192, maxTokens: 2048 }],
|
|
});
|
|
buildOllamaProviderMock
|
|
.mockResolvedValueOnce(discoveredFor("First tenant model"))
|
|
.mockResolvedValueOnce(discoveredFor("Second tenant model"));
|
|
|
|
for (const config of [configFor("first-tenant-access"), configFor("second-tenant-access")]) {
|
|
await provider.prepareDynamicModel?.(createDynamicModelContext(modelId, config) as never);
|
|
}
|
|
|
|
const resolveFor = (apiKey: string) =>
|
|
provider.resolveDynamicModel?.(
|
|
createDynamicModelContext(modelId, configFor(apiKey)) as never,
|
|
);
|
|
|
|
expect(resolveFor("first-tenant-access")?.name).toBe("First tenant model");
|
|
expect(resolveFor("second-tenant-access")?.name).toBe("Second tenant model");
|
|
expect(resolveFor("unprepared-tenant-access")).toBeUndefined();
|
|
expect(buildOllamaProviderMock).toHaveBeenNthCalledWith(1, baseUrl, {
|
|
quiet: true,
|
|
apiKey: "first-tenant-access",
|
|
});
|
|
expect(buildOllamaProviderMock).toHaveBeenNthCalledWith(2, baseUrl, {
|
|
quiet: true,
|
|
apiKey: "second-tenant-access",
|
|
});
|
|
});
|
|
|
|
it.each(["secretref-dynamic-access", "OLLAMA_API_KEY", OLLAMA_DEFAULT_API_KEY])(
|
|
"preserves opaque environment-backed SecretRef value %s for dynamic discovery",
|
|
async (secretValue) => {
|
|
const provider = registerProvider();
|
|
const baseUrl = "https://secretref-dynamic-ollama.example.com";
|
|
const envId = "VITEST_OLLAMA_DYNAMIC_DISCOVERY_KEY";
|
|
const previous = process.env[envId];
|
|
process.env[envId] = secretValue;
|
|
const config = {
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
baseUrl,
|
|
api: "ollama" as const,
|
|
apiKey: { source: "env" as const, provider: "default", id: envId },
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
mockDiscoveredOllamaProvider([], { baseUrl, once: true });
|
|
const context = createDynamicModelContext("secretref-dynamic-model", config);
|
|
|
|
try {
|
|
await provider.prepareDynamicModel?.(context as never);
|
|
|
|
expect(buildOllamaProviderMock).toHaveBeenCalledWith(baseUrl, {
|
|
quiet: true,
|
|
apiKey: secretValue,
|
|
});
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(
|
|
baseUrl,
|
|
"secretref-dynamic-model",
|
|
{ apiKey: secretValue },
|
|
);
|
|
} finally {
|
|
if (previous === undefined) {
|
|
delete process.env[envId];
|
|
} else {
|
|
process.env[envId] = previous;
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
it("fails closed when a dynamic Ollama SecretRef cannot be resolved", async () => {
|
|
const provider = registerProvider();
|
|
const envId = "VITEST_OLLAMA_DYNAMIC_MISSING_KEY";
|
|
const previous = process.env[envId];
|
|
delete process.env[envId];
|
|
const context = createDynamicModelContext("unreachable-private-model", {
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
baseUrl: "https://missing-secretref-ollama.example.com",
|
|
api: "ollama",
|
|
apiKey: { source: "env", provider: "default", id: envId },
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
try {
|
|
await provider.prepareDynamicModel?.(context as never);
|
|
|
|
expect(buildOllamaProviderMock).not.toHaveBeenCalled();
|
|
expect(queryOllamaModelShowInfoMock).not.toHaveBeenCalled();
|
|
} finally {
|
|
if (previous !== undefined) {
|
|
process.env[envId] = previous;
|
|
}
|
|
}
|
|
});
|
|
|
|
it("invalidates managed dynamic model caches when their SecretRef stops resolving", async () => {
|
|
const provider = registerProvider();
|
|
const baseUrl = "https://managed-dynamic-ollama.example.com";
|
|
const modelId = "managed-private-model";
|
|
const config = {
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
baseUrl,
|
|
api: "ollama" as const,
|
|
apiKey: { source: "file" as const, provider: "default", id: "/ollama/apiKey" },
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
resolveConfiguredSecretInputStringMock
|
|
.mockResolvedValueOnce({ value: "managed-dynamic-access" })
|
|
.mockResolvedValueOnce({ unresolvedRefReason: "managed credential is unavailable" });
|
|
mockDiscoveredOllamaProvider(
|
|
[{ id: modelId, name: "Managed private model", contextWindow: 8192 }],
|
|
{ baseUrl, once: true },
|
|
);
|
|
const context = createDynamicModelContext(modelId, config);
|
|
|
|
await provider.prepareDynamicModel?.(context as never);
|
|
expect(provider.resolveDynamicModel?.(context as never)?.id).toBe(modelId);
|
|
|
|
await provider.prepareDynamicModel?.(context as never);
|
|
|
|
expect(provider.resolveDynamicModel?.(context as never)).toBeUndefined();
|
|
expect(buildOllamaProviderMock).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("isolates identically named managed SecretRefs by their resolved configuration", async () => {
|
|
const provider = registerProvider();
|
|
const baseUrl = "https://shared-managed-ollama.example.com";
|
|
const modelId = "managed-tenant-model";
|
|
const configFor = (tenant: string) => ({
|
|
secrets: {
|
|
providers: {
|
|
default: { source: "file" as const, path: `/run/secrets/${tenant}.json` },
|
|
},
|
|
},
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
baseUrl,
|
|
api: "ollama" as const,
|
|
apiKey: { source: "file" as const, provider: "default", id: "/ollama/apiKey" },
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
const firstConfig = configFor("first-tenant");
|
|
const secondConfig = configFor("second-tenant");
|
|
resolveConfiguredSecretInputStringMock
|
|
.mockResolvedValueOnce({ value: "first-managed-tenant-access" })
|
|
.mockResolvedValueOnce({ value: "second-managed-tenant-access" });
|
|
mockDiscoveredOllamaProvider(
|
|
[{ id: modelId, name: "First managed tenant model", contextWindow: 8192 }],
|
|
{ baseUrl, once: true },
|
|
);
|
|
mockDiscoveredOllamaProvider(
|
|
[{ id: modelId, name: "Second managed tenant model", contextWindow: 8192 }],
|
|
{ baseUrl, once: true },
|
|
);
|
|
const contextFor = (config: typeof firstConfig) => createDynamicModelContext(modelId, config);
|
|
|
|
await provider.prepareDynamicModel?.(contextFor(firstConfig) as never);
|
|
await provider.prepareDynamicModel?.(contextFor(secondConfig) as never);
|
|
|
|
expect(provider.resolveDynamicModel?.(contextFor(firstConfig) as never)?.name).toBe(
|
|
"First managed tenant model",
|
|
);
|
|
expect(provider.resolveDynamicModel?.(contextFor(secondConfig) as never)?.name).toBe(
|
|
"Second managed tenant model",
|
|
);
|
|
expect(buildOllamaProviderMock).toHaveBeenNthCalledWith(1, baseUrl, {
|
|
quiet: true,
|
|
apiKey: "first-managed-tenant-access",
|
|
});
|
|
expect(buildOllamaProviderMock).toHaveBeenNthCalledWith(2, baseUrl, {
|
|
quiet: true,
|
|
apiKey: "second-managed-tenant-access",
|
|
});
|
|
});
|
|
|
|
it("resolves requested Ollama cloud models that are omitted from tags but confirmed by show", async () => {
|
|
const provider = registerProvider();
|
|
const previous = process.env.OLLAMA_API_KEY;
|
|
process.env.OLLAMA_API_KEY = "ollama-local";
|
|
mockDiscoveredOllamaProvider(
|
|
[
|
|
{
|
|
id: "kimi-k2.5:cloud",
|
|
name: "kimi-k2.5:cloud",
|
|
reasoning: true,
|
|
input: ["text"],
|
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
contextWindow: 262144,
|
|
maxTokens: 8192,
|
|
},
|
|
],
|
|
{ once: true },
|
|
);
|
|
mockOllamaShowInfo(["completion", "tools"]);
|
|
const context = createDynamicModelContext("deepseek-v4-pro:cloud");
|
|
|
|
try {
|
|
await provider.prepareDynamicModel?.(context as never);
|
|
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(
|
|
"http://127.0.0.1:11434",
|
|
"deepseek-v4-pro:cloud",
|
|
);
|
|
const resolved = provider.resolveDynamicModel?.(context as never);
|
|
expect(resolved?.provider).toBe("ollama");
|
|
expect(resolved?.id).toBe("deepseek-v4-pro:cloud");
|
|
expect(resolved?.api).toBe("ollama");
|
|
expect(resolved?.baseUrl).toBe("http://127.0.0.1:11434");
|
|
expect(resolved?.reasoning).toBe(true);
|
|
expect(resolved?.compat?.supportsTools).toBe(true);
|
|
} finally {
|
|
if (previous === undefined) {
|
|
delete process.env.OLLAMA_API_KEY;
|
|
} else {
|
|
process.env.OLLAMA_API_KEY = previous;
|
|
}
|
|
}
|
|
});
|
|
|
|
it("augments exact configured Ollama refs with live show capabilities", async () => {
|
|
const provider = registerProvider();
|
|
mockOllamaShowInfo(["completion", "tools", "thinking"]);
|
|
|
|
const rows = await augmentOllamaCatalog(provider, {
|
|
config: {
|
|
agents: {
|
|
defaults: {
|
|
models: {
|
|
"ollama/minimax-m3:cloud@work": {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(
|
|
"http://127.0.0.1:11434",
|
|
"minimax-m3:cloud",
|
|
);
|
|
expect(rows).toEqual([
|
|
expect.objectContaining({
|
|
provider: "ollama",
|
|
id: "minimax-m3:cloud",
|
|
api: "ollama",
|
|
reasoning: true,
|
|
contextWindow: 1_048_576,
|
|
compat: {
|
|
supportsTools: true,
|
|
supportsUsageInStreaming: true,
|
|
},
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it("augments Ollama fallback and per-agent configured refs", async () => {
|
|
const provider = registerProvider();
|
|
mockOllamaShowInfo(["completion", "thinking"], { once: false });
|
|
|
|
const rows = await augmentOllamaCatalog(provider, {
|
|
config: {
|
|
agents: {
|
|
defaults: {
|
|
heartbeat: {
|
|
model: "ollama/heartbeat:cloud",
|
|
},
|
|
model: {
|
|
primary: "openai/gpt-5.5",
|
|
fallbacks: ["ollama/global-fallback:cloud"],
|
|
},
|
|
},
|
|
entries: {
|
|
ops: {
|
|
model: {
|
|
primary: "ollama/per-agent:cloud@work",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(
|
|
"http://127.0.0.1:11434",
|
|
"global-fallback:cloud",
|
|
);
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(
|
|
"http://127.0.0.1:11434",
|
|
"per-agent:cloud",
|
|
);
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(
|
|
"http://127.0.0.1:11434",
|
|
"heartbeat:cloud",
|
|
);
|
|
expect(rows).toEqual([
|
|
expect.objectContaining({
|
|
provider: "ollama",
|
|
id: "global-fallback:cloud",
|
|
reasoning: true,
|
|
contextWindow: 1_048_576,
|
|
}),
|
|
expect.objectContaining({
|
|
provider: "ollama",
|
|
id: "heartbeat:cloud",
|
|
reasoning: true,
|
|
contextWindow: 1_048_576,
|
|
}),
|
|
expect.objectContaining({
|
|
provider: "ollama",
|
|
id: "per-agent:cloud",
|
|
reasoning: true,
|
|
contextWindow: 1_048_576,
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
name: "augments configured Ollama Cloud refs with resolved auth",
|
|
baseUrl: "https://ollama.com",
|
|
modelId: "cloud-new:cloud",
|
|
resolvedApiKey: "cloud-key",
|
|
expectedApiKey: "cloud-key",
|
|
capabilities: ["completion", "thinking"],
|
|
},
|
|
{
|
|
name: "augments configured remote Ollama refs with configured auth",
|
|
baseUrl: "https://ollama.example.test",
|
|
modelId: "remote-new",
|
|
configuredApiKey: "remote-key",
|
|
resolvedApiKey: "",
|
|
expectedApiKey: "remote-key",
|
|
capabilities: ["completion", "tools", "thinking"],
|
|
},
|
|
])(
|
|
"$name",
|
|
async ({
|
|
baseUrl,
|
|
modelId,
|
|
configuredApiKey,
|
|
resolvedApiKey,
|
|
expectedApiKey,
|
|
capabilities,
|
|
}) => {
|
|
const provider = registerProvider();
|
|
mockOllamaShowInfo(capabilities);
|
|
|
|
const rows = await augmentOllamaCatalog(provider, {
|
|
config: {
|
|
agents: {
|
|
defaults: {
|
|
models: {
|
|
[`ollama/${modelId}`]: {},
|
|
},
|
|
},
|
|
},
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
baseUrl,
|
|
api: "ollama",
|
|
...(configuredApiKey ? { apiKey: configuredApiKey } : {}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: {},
|
|
resolveProviderApiKey: vi.fn(() => ({ apiKey: resolvedApiKey })),
|
|
});
|
|
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(baseUrl, modelId, {
|
|
apiKey: expectedApiKey,
|
|
});
|
|
expect(rows).toEqual([
|
|
expect.objectContaining({
|
|
provider: "ollama",
|
|
id: modelId,
|
|
reasoning: true,
|
|
contextWindow: 1_048_576,
|
|
}),
|
|
]);
|
|
},
|
|
);
|
|
|
|
it.each(["$OLLAMA_API_KEY", "${OLLAMA_API_KEY}"])(
|
|
"resolves configured Ollama Cloud SecretInput auth string %s",
|
|
async (apiKeyRef) => {
|
|
const provider = registerProvider();
|
|
mockOllamaShowInfo(["completion", "thinking"]);
|
|
|
|
await augmentOllamaCatalog(provider, {
|
|
config: {
|
|
agents: {
|
|
defaults: {
|
|
models: {
|
|
"ollama/cloud-new:cloud": {},
|
|
},
|
|
},
|
|
},
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
baseUrl: "https://ollama.com",
|
|
api: "ollama",
|
|
apiKey: apiKeyRef,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: { OLLAMA_API_KEY: "cloud-key" },
|
|
resolveProviderApiKey: vi.fn(() => ({
|
|
apiKey: "OLLAMA_API_KEY",
|
|
discoveryApiKey: "cloud-key",
|
|
})),
|
|
});
|
|
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(
|
|
"https://ollama.com",
|
|
"cloud-new:cloud",
|
|
{ apiKey: "cloud-key" },
|
|
);
|
|
queryOllamaModelShowInfoMock.mockClear();
|
|
},
|
|
);
|
|
|
|
it("augments secured local Ollama refs with resolved configured auth", async () => {
|
|
const provider = registerProvider();
|
|
mockOllamaShowInfo(["completion", "tools", "thinking"]);
|
|
|
|
await augmentOllamaCatalog(provider, {
|
|
config: {
|
|
agents: {
|
|
defaults: {
|
|
models: {
|
|
"ollama/local-secured": {},
|
|
},
|
|
},
|
|
},
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
baseUrl: "http://127.0.0.1:11434",
|
|
api: "ollama",
|
|
apiKey: { source: "env", provider: "default", id: "LOCAL_OLLAMA_API_KEY" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: {
|
|
LOCAL_OLLAMA_API_KEY: "local-key",
|
|
OLLAMA_API_KEY: "ambient-cloud-key",
|
|
},
|
|
resolveProviderApiKey: vi.fn(() => ({
|
|
apiKey: "LOCAL_OLLAMA_API_KEY",
|
|
discoveryApiKey: "local-key",
|
|
})),
|
|
});
|
|
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(
|
|
"http://127.0.0.1:11434",
|
|
"local-secured",
|
|
{ apiKey: "local-key" },
|
|
);
|
|
});
|
|
|
|
it("does not attach ambient OLLAMA_API_KEY to local show probes", async () => {
|
|
const provider = registerProvider();
|
|
mockOllamaShowInfo(["completion", "tools"]);
|
|
|
|
await augmentOllamaCatalog(provider, {
|
|
config: {
|
|
agents: {
|
|
defaults: {
|
|
models: {
|
|
"ollama/local-open": {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: {
|
|
OLLAMA_API_KEY: "ambient-cloud-key",
|
|
},
|
|
resolveProviderApiKey: vi.fn(() => ({
|
|
apiKey: "OLLAMA_API_KEY",
|
|
discoveryApiKey: "ambient-cloud-key",
|
|
})),
|
|
});
|
|
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(
|
|
"http://127.0.0.1:11434",
|
|
"local-open",
|
|
);
|
|
});
|
|
|
|
it("augments configured first-class Ollama Cloud provider refs", async () => {
|
|
const provider = registerOllamaCloudProvider();
|
|
mockOllamaShowInfo(["completion", "thinking"]);
|
|
|
|
const rows = await augmentOllamaCatalog(provider, {
|
|
config: {
|
|
agents: {
|
|
defaults: {
|
|
models: {
|
|
"ollama-cloud/cloud-new:cloud": {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: {},
|
|
resolveProviderApiKey: vi.fn(() => ({ apiKey: "cloud-key" })),
|
|
});
|
|
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(
|
|
"https://ollama.com",
|
|
"cloud-new:cloud",
|
|
{ apiKey: "cloud-key" },
|
|
);
|
|
expect(rows).toEqual([
|
|
expect.objectContaining({
|
|
provider: "ollama-cloud",
|
|
id: "cloud-new:cloud",
|
|
reasoning: true,
|
|
contextWindow: 1_048_576,
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it("prefers explicit Ollama Cloud provider keys over local env markers", async () => {
|
|
const provider = registerOllamaCloudProvider();
|
|
mockOllamaShowInfo(["completion", "thinking"]);
|
|
|
|
await augmentOllamaCatalog(provider, {
|
|
config: {
|
|
agents: {
|
|
defaults: {
|
|
models: {
|
|
"ollama-cloud/cloud-new:cloud": {},
|
|
},
|
|
},
|
|
},
|
|
models: {
|
|
providers: {
|
|
"ollama-cloud": {
|
|
baseUrl: "https://ollama.com",
|
|
api: "ollama",
|
|
apiKey: "cloud-config-key",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: { OLLAMA_API_KEY: "ollama-local" },
|
|
resolveProviderApiKey: vi.fn(() => ({ apiKey: "ollama-local" })),
|
|
});
|
|
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(
|
|
"https://ollama.com",
|
|
"cloud-new:cloud",
|
|
{ apiKey: "cloud-config-key" },
|
|
);
|
|
});
|
|
|
|
it("uses resolved discovery auth instead of non-secret markers for Ollama Cloud probes", async () => {
|
|
const provider = registerOllamaCloudProvider();
|
|
mockOllamaShowInfo(["completion", "thinking"]);
|
|
|
|
await augmentOllamaCatalog(provider, {
|
|
config: {
|
|
agents: {
|
|
defaults: {
|
|
models: {
|
|
"ollama-cloud/cloud-new:cloud": {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: {},
|
|
resolveProviderApiKey: vi.fn(() => ({
|
|
apiKey: "secretref-managed", // pragma: allowlist secret
|
|
discoveryApiKey: "cloud-key",
|
|
})),
|
|
});
|
|
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(
|
|
"https://ollama.com",
|
|
"cloud-new:cloud",
|
|
{ apiKey: "cloud-key" },
|
|
);
|
|
});
|
|
|
|
it("does not probe Ollama Cloud catalog with non-secret auth markers", async () => {
|
|
const provider = registerOllamaCloudProvider();
|
|
|
|
const rows = await augmentOllamaCatalog(provider, {
|
|
config: {
|
|
agents: {
|
|
defaults: {
|
|
models: {
|
|
"ollama-cloud/cloud-new:cloud": {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: { OLLAMA_API_KEY: "secretref-managed" }, // pragma: allowlist secret
|
|
resolveProviderApiKey: vi.fn(() => ({
|
|
apiKey: "secretref-managed", // pragma: allowlist secret
|
|
})),
|
|
});
|
|
|
|
expect(queryOllamaModelShowInfoMock).not.toHaveBeenCalled();
|
|
expect(rows).toEqual([]);
|
|
});
|
|
|
|
it("augments id-only configured Ollama provider rows with live show capabilities", async () => {
|
|
const provider = registerProvider();
|
|
mockOllamaShowInfo(["completion", "tools", "thinking", "vision"]);
|
|
|
|
const rows = await augmentOllamaCatalog(provider, {
|
|
entries: [
|
|
{
|
|
provider: "ollama",
|
|
id: "minimax-m3:cloud",
|
|
name: "Configured Minimax M3",
|
|
api: "openai-completions",
|
|
},
|
|
],
|
|
});
|
|
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(
|
|
"http://127.0.0.1:11434",
|
|
"minimax-m3:cloud",
|
|
);
|
|
expect(rows).toEqual([
|
|
expect.objectContaining({
|
|
provider: "ollama",
|
|
id: "minimax-m3:cloud",
|
|
name: "Configured Minimax M3",
|
|
api: "openai-completions",
|
|
reasoning: true,
|
|
input: ["text", "image"],
|
|
contextWindow: 1_048_576,
|
|
compat: {
|
|
supportsTools: true,
|
|
supportsUsageInStreaming: true,
|
|
},
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it("fills missing metadata on partially configured Ollama provider rows", async () => {
|
|
const provider = registerProvider();
|
|
mockOllamaShowInfo(["completion", "tools", "thinking", "vision"]);
|
|
|
|
const rows = await augmentOllamaCatalog(provider, {
|
|
entries: [
|
|
{
|
|
provider: "ollama",
|
|
id: "minimax-m3:cloud",
|
|
name: "minimax-m3:cloud",
|
|
contextWindow: 128_000,
|
|
},
|
|
],
|
|
});
|
|
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith(
|
|
"http://127.0.0.1:11434",
|
|
"minimax-m3:cloud",
|
|
);
|
|
expect(rows).toEqual([
|
|
expect.objectContaining({
|
|
provider: "ollama",
|
|
id: "minimax-m3:cloud",
|
|
reasoning: true,
|
|
input: ["text", "image"],
|
|
compat: {
|
|
supportsTools: true,
|
|
supportsUsageInStreaming: true,
|
|
},
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it("does not override configured Ollama provider metadata", async () => {
|
|
const provider = registerProvider();
|
|
|
|
const rows = await augmentOllamaCatalog(provider, {
|
|
entries: [
|
|
{
|
|
provider: "ollama",
|
|
id: "minimax-m3:cloud",
|
|
name: "minimax-m3:cloud",
|
|
contextWindow: 128_000,
|
|
reasoning: false,
|
|
input: ["text"],
|
|
compat: { supportsTools: false },
|
|
},
|
|
],
|
|
});
|
|
|
|
expect(queryOllamaModelShowInfoMock).not.toHaveBeenCalled();
|
|
expect(rows).toEqual([]);
|
|
});
|
|
|
|
it("bounds configured Ollama show probes", async () => {
|
|
const provider = registerProvider();
|
|
let active = 0;
|
|
let maxActive = 0;
|
|
queryOllamaModelShowInfoMock.mockImplementation(async () => {
|
|
active += 1;
|
|
maxActive = Math.max(maxActive, active);
|
|
await new Promise<void>((resolve) => {
|
|
setTimeout(resolve, 0);
|
|
});
|
|
active -= 1;
|
|
return {
|
|
contextWindow: 1_048_576,
|
|
capabilities: ["completion", "thinking"],
|
|
};
|
|
});
|
|
|
|
const rows = await augmentOllamaCatalog(provider, {
|
|
entries: Array.from({ length: 5 }, (_, index) => ({
|
|
provider: "ollama",
|
|
id: `model-${index}:cloud`,
|
|
name: `model-${index}:cloud`,
|
|
})),
|
|
});
|
|
|
|
expect(rows).toHaveLength(5);
|
|
expect(maxActive).toBeGreaterThan(1);
|
|
expect(maxActive).toBeLessThanOrEqual(4);
|
|
});
|
|
|
|
it("caps configured Ollama show probes", async () => {
|
|
const provider = registerProvider();
|
|
mockOllamaShowInfo(["completion", "thinking"], { once: false });
|
|
|
|
const rows = await augmentOllamaCatalog(provider, {
|
|
entries: Array.from({ length: 10 }, (_, index) => ({
|
|
provider: "ollama",
|
|
id: `model-${index}:cloud`,
|
|
name: `model-${index}:cloud`,
|
|
})),
|
|
});
|
|
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledTimes(8);
|
|
expect(rows).toHaveLength(8);
|
|
});
|
|
|
|
it("keeps unknown requested Ollama models unresolved when show inspection fails", async () => {
|
|
const provider = registerProvider();
|
|
const previous = process.env.OLLAMA_API_KEY;
|
|
process.env.OLLAMA_API_KEY = "ollama-local";
|
|
mockDiscoveredOllamaProvider([], { once: true });
|
|
queryOllamaModelShowInfoMock.mockResolvedValueOnce({ showInspectionFailed: true });
|
|
const context = createDynamicModelContext("depseek-v4-pro:cloud");
|
|
|
|
try {
|
|
await provider.prepareDynamicModel?.(context as never);
|
|
|
|
expect(provider.resolveDynamicModel?.(context as never)).toBeUndefined();
|
|
} finally {
|
|
if (previous === undefined) {
|
|
delete process.env.OLLAMA_API_KEY;
|
|
} else {
|
|
process.env.OLLAMA_API_KEY = previous;
|
|
}
|
|
}
|
|
});
|
|
|
|
it("skips implicit localhost discovery when a custom remote Ollama provider is configured", async () => {
|
|
const provider = registerProvider();
|
|
|
|
const result = await provider.catalog.run({
|
|
config: {
|
|
models: {
|
|
providers: {
|
|
"ollama-cloud": {
|
|
api: "ollama",
|
|
baseUrl: "https://ollama.com",
|
|
models: [{ id: "kimi-k2.5", name: "Kimi K2.5" }],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: { NODE_ENV: "development", OLLAMA_API_KEY: "ollama-live" },
|
|
resolveProviderApiKey: () => ({ apiKey: "ollama-live" }),
|
|
} as never);
|
|
|
|
expect(result).toBeNull();
|
|
expect(buildOllamaProviderMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it.each(["docker.orb.internal", "host.docker.internal", "host.orb.internal"])(
|
|
"skips implicit localhost discovery when a custom host-backed Ollama provider is configured for %s",
|
|
async (hostname) => {
|
|
const provider = registerProvider();
|
|
|
|
const result = await provider.catalog.run({
|
|
config: {
|
|
models: {
|
|
providers: {
|
|
"ollama-orb": {
|
|
api: "ollama",
|
|
baseUrl: `http://${hostname}:11434`,
|
|
models: [{ id: "qwen3.5:27b", name: "Qwen 3.5 27B" }],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: { NODE_ENV: "development", OLLAMA_API_KEY: "ollama-live" },
|
|
resolveProviderApiKey: () => ({ apiKey: "ollama-live" }),
|
|
} as never);
|
|
|
|
expect(result).toBeNull();
|
|
expect(buildOllamaProviderMock).not.toHaveBeenCalled();
|
|
},
|
|
);
|
|
|
|
it("treats custom 127/8 Ollama providers as loopback for implicit discovery", async () => {
|
|
const provider = registerProvider();
|
|
mockDiscoveredOllamaProvider([], { once: true });
|
|
|
|
const result = await provider.catalog.run({
|
|
config: {
|
|
models: {
|
|
providers: {
|
|
"ollama-alt-local": {
|
|
api: "ollama",
|
|
baseUrl: "http://127.0.0.2:11434",
|
|
models: [{ id: "llama3.2", name: "Llama 3.2" }],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
env: { NODE_ENV: "development", OLLAMA_API_KEY: "ollama-live" },
|
|
resolveProviderApiKey: () => ({ apiKey: "ollama-live" }),
|
|
} as never);
|
|
|
|
const resultProvider = requireRecord(result?.provider, "catalog provider");
|
|
expect(resultProvider.baseUrl).toBe("http://127.0.0.1:11434");
|
|
expect(resultProvider.api).toBe("ollama");
|
|
expect(buildOllamaProviderMock).toHaveBeenCalledWith(undefined, {
|
|
quiet: false,
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
name: "does not mint synthetic auth for empty default-ish provider stubs",
|
|
providerPatch: { baseUrl: "http://127.0.0.1:11434" },
|
|
mintsAuth: false,
|
|
},
|
|
{
|
|
name: "mints synthetic auth for non-default explicit ollama config",
|
|
providerPatch: { baseUrl: "http://remote-ollama:11434" },
|
|
mintsAuth: true,
|
|
},
|
|
{
|
|
name: "mints synthetic auth for non-default baseURL alias config",
|
|
providerPatch: { baseURL: "http://remote-ollama:11434" },
|
|
mintsAuth: true,
|
|
},
|
|
{
|
|
name: "does not mint synthetic auth for Ollama Cloud baseUrl",
|
|
providerPatch: { baseUrl: "https://ollama.com" },
|
|
mintsAuth: false,
|
|
},
|
|
])("$name", ({ providerPatch, mintsAuth }) => {
|
|
const provider = registerProvider();
|
|
const auth = provider.resolveSyntheticAuth?.({
|
|
providerConfig: {
|
|
api: "ollama",
|
|
models: [],
|
|
...providerPatch,
|
|
} as never,
|
|
});
|
|
if (mintsAuth) {
|
|
expect(auth).toEqual({
|
|
apiKey: "ollama-local",
|
|
source: "models.providers.ollama (synthetic local key)",
|
|
mode: "api-key",
|
|
});
|
|
} else {
|
|
expect(auth).toBeUndefined();
|
|
}
|
|
});
|
|
|
|
it("registers ollama-cloud as a hosted provider", async () => {
|
|
const provider = registerOllamaCloudProvider();
|
|
|
|
expect(provider.id).toBe("ollama-cloud");
|
|
expect(provider.envVars).toEqual(["OLLAMA_API_KEY"]);
|
|
expect(provider.auth?.map((method: { id: string }) => method.id)).toEqual(["api-key"]);
|
|
|
|
const result = await provider.staticCatalog?.run({
|
|
config: {},
|
|
env: {},
|
|
resolveProviderApiKey: () => ({}),
|
|
} as never);
|
|
if (!result || !("provider" in result)) {
|
|
throw new Error("single provider catalog result missing");
|
|
}
|
|
expect(result.provider.baseUrl).toBe("https://ollama.com");
|
|
expect(result.provider.models?.map((model: { id: string }) => model.id)).toEqual([
|
|
"minimax-m2.7",
|
|
"glm-5.1",
|
|
"glm-5.2",
|
|
]);
|
|
expect(result.provider.models).toEqual([
|
|
expect.objectContaining({
|
|
id: "minimax-m2.7",
|
|
contextWindow: 196_608,
|
|
reasoning: true,
|
|
input: ["text"],
|
|
compat: { supportsTools: true, supportsUsageInStreaming: true },
|
|
}),
|
|
expect.objectContaining({
|
|
id: "glm-5.1",
|
|
contextWindow: 202_752,
|
|
reasoning: true,
|
|
input: ["text"],
|
|
compat: { supportsTools: true, supportsUsageInStreaming: true },
|
|
}),
|
|
expect.objectContaining({
|
|
id: "glm-5.2",
|
|
contextWindow: 1_000_000,
|
|
reasoning: true,
|
|
input: ["text"],
|
|
compat: { supportsTools: true, supportsUsageInStreaming: true },
|
|
}),
|
|
]);
|
|
|
|
provider.createStreamFn?.({
|
|
config: {},
|
|
model: { api: "ollama", id: "kimi-k2.5:cloud" },
|
|
provider: "ollama-cloud",
|
|
} as never);
|
|
expect(requireConfiguredStreamParams().providerBaseUrl).toBe("https://ollama.com");
|
|
});
|
|
|
|
it("uses Ollama Cloud auth for live catalog discovery", async () => {
|
|
const provider = registerOllamaCloudProvider();
|
|
mockDiscoveredOllamaProvider([buildOllamaModelDefinitionMock("glm-5.2")], {
|
|
baseUrl: "https://ollama.com",
|
|
once: true,
|
|
});
|
|
|
|
const result = await provider.catalog.run({
|
|
config: {},
|
|
env: {},
|
|
resolveProviderApiKey: () => ({
|
|
apiKey: "OLLAMA_API_KEY",
|
|
discoveryApiKey: "cloud-key",
|
|
}),
|
|
} as never);
|
|
|
|
expect(buildOllamaProviderMock).toHaveBeenCalledWith("https://ollama.com", {
|
|
apiKey: "cloud-key",
|
|
quiet: true,
|
|
});
|
|
expect(result?.provider.apiKey).toBe("OLLAMA_API_KEY");
|
|
expect(result?.provider.models).toEqual(
|
|
expect.arrayContaining([expect.objectContaining({ id: "glm-5.2", name: "glm-5.2" })]),
|
|
);
|
|
});
|
|
|
|
it("confirms GLM-5.2 with authenticated show when cloud tags omit it", async () => {
|
|
const provider = registerOllamaCloudProvider();
|
|
mockDiscoveredOllamaProvider([buildOllamaModelDefinitionMock("kimi-k2.6")], {
|
|
baseUrl: "https://ollama.com",
|
|
once: true,
|
|
});
|
|
mockOllamaShowInfo(["completion", "thinking", "tools"], { contextWindow: 1_000_000 });
|
|
const result = await provider.catalog.run({
|
|
config: {
|
|
agents: { defaults: { model: { primary: "ollama-cloud/glm-5.2" } } },
|
|
},
|
|
env: {},
|
|
resolveProviderApiKey: () => ({
|
|
apiKey: "secretref-managed",
|
|
discoveryApiKey: "cloud-key",
|
|
}),
|
|
} as never);
|
|
|
|
expect(buildOllamaProviderMock).toHaveBeenCalledWith("https://ollama.com", {
|
|
apiKey: "cloud-key",
|
|
quiet: true,
|
|
});
|
|
expect(queryOllamaModelShowInfoMock).toHaveBeenCalledWith("https://ollama.com", "glm-5.2", {
|
|
apiKey: "cloud-key",
|
|
});
|
|
expect(result?.provider.models).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
id: "glm-5.2",
|
|
contextWindow: 1_000_000,
|
|
maxTokens: 8192,
|
|
reasoning: true,
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("resolves GLM-5.2 from the cloud fallback catalog", () => {
|
|
const provider = registerOllamaCloudProvider();
|
|
const model = provider.resolveDynamicModel?.({
|
|
provider: "ollama-cloud",
|
|
modelId: "glm-5.2",
|
|
} as never);
|
|
|
|
expect(model).toEqual(
|
|
expect.objectContaining({
|
|
provider: "ollama-cloud",
|
|
id: "glm-5.2",
|
|
contextWindow: 1_000_000,
|
|
maxTokens: 8192,
|
|
reasoning: true,
|
|
}),
|
|
);
|
|
expect(model?.contextTokens).toBeUndefined();
|
|
});
|
|
|
|
it("does not mint synthetic auth for public IPv4 baseUrl", () => {
|
|
const provider = registerProvider();
|
|
|
|
const auth = provider.resolveSyntheticAuth?.({
|
|
providerConfig: {
|
|
baseUrl: "http://8.8.8.8:11434",
|
|
api: "ollama",
|
|
models: [],
|
|
},
|
|
});
|
|
|
|
expect(auth).toBeUndefined();
|
|
});
|
|
|
|
it("wraps OpenAI-compatible payloads with num_ctx for Ollama compat routes", () => {
|
|
const provider = registerProvider();
|
|
let payloadSeen: Record<string, unknown> | undefined;
|
|
const baseStreamFn = vi.fn((_model, _context, options) => {
|
|
const payload: Record<string, unknown> = { options: { temperature: 0.1 } };
|
|
options?.onPayload?.(payload, _model);
|
|
payloadSeen = payload;
|
|
return {} as never;
|
|
});
|
|
|
|
const wrapped = provider.wrapStreamFn?.({
|
|
config: {
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
api: "openai-completions",
|
|
baseUrl: "http://127.0.0.1:11434/v1",
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
provider: "ollama",
|
|
modelId: "qwen3:32b",
|
|
model: {
|
|
api: "openai-completions",
|
|
provider: "ollama",
|
|
id: "qwen3:32b",
|
|
baseUrl: "http://127.0.0.1:11434/v1",
|
|
contextWindow: 202_752,
|
|
contextTokens: 32_768,
|
|
},
|
|
streamFn: baseStreamFn,
|
|
});
|
|
|
|
if (!wrapped) {
|
|
throw new Error("expected Ollama OpenAI-compatible stream wrapper");
|
|
}
|
|
void wrapped({} as never, {} as never, {});
|
|
expect(baseStreamFn).toHaveBeenCalledTimes(1);
|
|
expect((payloadSeen?.options as Record<string, unknown> | undefined)?.num_ctx).toBe(32_768);
|
|
});
|
|
|
|
it("owns replay policy for OpenAI-compatible and native Ollama routes", () => {
|
|
const provider = registerProvider();
|
|
|
|
const openAiCompatPolicy = provider.buildReplayPolicy?.({
|
|
provider: "ollama",
|
|
modelApi: "openai-completions",
|
|
modelId: "qwen3:32b",
|
|
} as never);
|
|
expect(openAiCompatPolicy?.sanitizeToolCallIds).toBe(true);
|
|
expect(openAiCompatPolicy?.toolCallIdMode).toBe("strict");
|
|
expect(openAiCompatPolicy?.applyAssistantFirstOrderingFix).toBe(true);
|
|
expect(openAiCompatPolicy?.validateGeminiTurns).toBe(true);
|
|
expect(openAiCompatPolicy?.validateAnthropicTurns).toBe(true);
|
|
|
|
const responsesPolicy = provider.buildReplayPolicy?.({
|
|
provider: "ollama",
|
|
modelApi: "openai-responses",
|
|
modelId: "qwen3:32b",
|
|
} as never);
|
|
expect(responsesPolicy?.sanitizeToolCallIds).toBe(true);
|
|
expect(responsesPolicy?.toolCallIdMode).toBe("strict");
|
|
expect(responsesPolicy?.applyAssistantFirstOrderingFix).toBe(false);
|
|
expect(responsesPolicy?.validateGeminiTurns).toBe(false);
|
|
expect(responsesPolicy?.validateAnthropicTurns).toBe(false);
|
|
|
|
const nativePolicy = provider.buildReplayPolicy?.({
|
|
provider: "ollama",
|
|
modelApi: "ollama",
|
|
modelId: "qwen3.5:9b",
|
|
} as never);
|
|
expect(nativePolicy?.sanitizeToolCallIds).toBe(false);
|
|
expect(nativePolicy?.toolCallIdMode).toBeUndefined();
|
|
expect(nativePolicy?.applyAssistantFirstOrderingFix).toBe(true);
|
|
expect(nativePolicy?.validateGeminiTurns).toBe(true);
|
|
expect(nativePolicy?.validateAnthropicTurns).toBe(true);
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
providerId: "ollama",
|
|
register: registerProvider,
|
|
nativeBaseUrl: "http://127.0.0.1:11434",
|
|
},
|
|
{
|
|
providerId: "ollama-cloud",
|
|
register: registerOllamaCloudProvider,
|
|
nativeBaseUrl: "https://ollama.com",
|
|
},
|
|
])(
|
|
"$providerId selects native /api/chat transport only for api=ollama",
|
|
({ providerId, register, nativeBaseUrl }) => {
|
|
const provider = register();
|
|
const createStream = (api: "ollama" | "openai-completions", baseUrl: string) =>
|
|
provider.createStreamFn?.({
|
|
config: {
|
|
models: {
|
|
providers: {
|
|
[providerId]: {
|
|
api,
|
|
baseUrl,
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
model: {
|
|
api,
|
|
id: "qwen3:32b",
|
|
provider: providerId,
|
|
},
|
|
provider: providerId,
|
|
} as never);
|
|
|
|
const compatibleStream = createStream("openai-completions", `${nativeBaseUrl}/v1`);
|
|
|
|
expect(compatibleStream).toBeUndefined();
|
|
expect(createConfiguredOllamaStreamFnMock).not.toHaveBeenCalled();
|
|
|
|
const nativeStream = createStream("ollama", nativeBaseUrl);
|
|
|
|
expect(nativeStream).toBeDefined();
|
|
expect(createConfiguredOllamaStreamFnMock).toHaveBeenCalledOnce();
|
|
expect(requireConfiguredStreamParams().providerBaseUrl).toBe(nativeBaseUrl);
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
{
|
|
name: "routes createStreamFn to the correct provider baseUrl for ollama2",
|
|
providerId: "ollama2",
|
|
baseUrlKey: "baseUrl",
|
|
expectedBaseUrl: "http://127.0.0.1:11435",
|
|
},
|
|
{
|
|
name: "routes createStreamFn through baseURL alias for custom Ollama providers",
|
|
providerId: "ollama2",
|
|
baseUrlKey: "baseURL",
|
|
expectedBaseUrl: "http://127.0.0.1:11435",
|
|
},
|
|
{
|
|
name: "uses ollama provider baseUrl when provider is ollama (backward compat)",
|
|
providerId: "ollama",
|
|
baseUrlKey: "baseUrl",
|
|
expectedBaseUrl: "http://127.0.0.1:11434",
|
|
},
|
|
])("$name", ({ providerId, baseUrlKey, expectedBaseUrl }) => {
|
|
const provider = registerProvider();
|
|
const config = {
|
|
models: {
|
|
providers: {
|
|
ollama: {
|
|
api: "ollama",
|
|
baseUrl: "http://127.0.0.1:11434",
|
|
models: [],
|
|
},
|
|
ollama2: {
|
|
api: "ollama",
|
|
[baseUrlKey]: "http://127.0.0.1:11435",
|
|
models: [],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
const model = { id: "llama3.2", provider: providerId, api: "ollama", baseUrl: undefined };
|
|
|
|
provider.createStreamFn?.({ config, model, provider: providerId } as never);
|
|
|
|
expect(requireConfiguredStreamParams().providerBaseUrl).toBe(expectedBaseUrl);
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
name: "wraps native Ollama payloads with top-level think=false when thinking is off",
|
|
thinkingLevel: "off" as const,
|
|
expectedThink: false,
|
|
},
|
|
...(["low", "medium", "high"] as const).map((thinkingLevel) => ({
|
|
name: `preserves native Ollama ${thinkingLevel} thinking on the wire`,
|
|
thinkingLevel,
|
|
expectedThink: thinkingLevel,
|
|
})),
|
|
{
|
|
name: "keeps the compatible local Ollama max mapping",
|
|
thinkingLevel: "max" as const,
|
|
expectedThink: "high",
|
|
},
|
|
{
|
|
name: "does not set think param when thinkingLevel is undefined",
|
|
thinkingLevel: undefined,
|
|
expectedThink: undefined,
|
|
},
|
|
])("$name", ({ thinkingLevel, expectedThink }) => {
|
|
const { baseStreamFn, payloadSeen } = captureWrappedOllamaPayload(thinkingLevel);
|
|
expect(baseStreamFn).toHaveBeenCalledTimes(1);
|
|
expect(payloadSeen?.think).toBe(expectedThink);
|
|
expect((payloadSeen?.options as Record<string, unknown> | undefined)?.think).toBeUndefined();
|
|
});
|
|
|
|
it("preserves native Ollama Cloud max thinking on the wire", () => {
|
|
const { payloadSeen } = captureWrappedOllamaPayload("max", {
|
|
provider: "ollama-cloud",
|
|
modelId: "glm-5.2",
|
|
baseUrl: "https://ollama.com",
|
|
});
|
|
|
|
expect(payloadSeen?.think).toBe("max");
|
|
});
|
|
|
|
it("keeps native Ollama thinking off by default while exposing opt-in effort levels", () => {
|
|
const provider = registerProvider();
|
|
|
|
expect(
|
|
provider.resolveThinkingProfile?.({
|
|
provider: "ollama",
|
|
modelId: "llama3.2:latest",
|
|
reasoning: false,
|
|
}),
|
|
).toEqual({
|
|
levels: [{ id: "off" }],
|
|
defaultLevel: "off",
|
|
});
|
|
|
|
expect(
|
|
provider.resolveThinkingProfile?.({
|
|
provider: "ollama",
|
|
modelId: "gemma4:31b",
|
|
reasoning: true,
|
|
}),
|
|
).toEqual({
|
|
levels: [{ id: "off" }, { id: "low" }, { id: "medium" }, { id: "high" }, { id: "max" }],
|
|
defaultLevel: "off",
|
|
});
|
|
});
|
|
|
|
it("registers an image-capable media understanding provider so image tool can route ollama/*", () => {
|
|
const mediaProviders: Array<{
|
|
id: string;
|
|
capabilities?: string[];
|
|
defaultModels?: Record<string, string>;
|
|
autoPriority?: Record<string, number>;
|
|
describeImage?: unknown;
|
|
describeImages?: unknown;
|
|
}> = [];
|
|
|
|
plugin.register(
|
|
createTestPluginApi({
|
|
id: "ollama",
|
|
name: "Ollama",
|
|
source: "test",
|
|
config: {},
|
|
pluginConfig: {},
|
|
runtime: {} as never,
|
|
registerProvider() {},
|
|
registerMediaUnderstandingProvider(provider) {
|
|
mediaProviders.push(provider);
|
|
},
|
|
}),
|
|
);
|
|
|
|
expect(mediaProviders).toHaveLength(1);
|
|
const ollamaMedia = expectDefined(mediaProviders[0], "Ollama media provider");
|
|
expect(ollamaMedia.id).toBe("ollama");
|
|
expect(ollamaMedia.capabilities).toEqual(["image"]);
|
|
expect(ollamaMedia.describeImage).toBeTypeOf("function");
|
|
expect(ollamaMedia.describeImages).toBeTypeOf("function");
|
|
// Intentional: no defaultModels or autoPriority. Ollama vision models are
|
|
// user-installed (llava, qwen2.5vl, …) with no universal default, and we
|
|
// don't want Ollama to auto-steal image duty from configured providers.
|
|
expect(ollamaMedia.defaultModels).toBeUndefined();
|
|
expect(ollamaMedia.autoPriority).toBeUndefined();
|
|
});
|
|
});
|
|
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|