mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 01:31:08 +00:00
fix: quiet unconfigured ollama discovery
This commit is contained in:
@@ -139,6 +139,125 @@ describe("ollama plugin", () => {
|
||||
expect(buildOllamaProviderMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps empty default-ish provider stubs quiet", async () => {
|
||||
const provider = registerProvider();
|
||||
buildOllamaProviderMock.mockResolvedValueOnce({
|
||||
baseUrl: "http://127.0.0.1:11434",
|
||||
api: "ollama",
|
||||
models: [],
|
||||
});
|
||||
|
||||
const result = await provider.discovery.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).toHaveBeenCalledWith("http://127.0.0.1:11434", {
|
||||
quiet: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("treats non-default baseUrl as explicit discovery config", async () => {
|
||||
const provider = registerProvider();
|
||||
buildOllamaProviderMock.mockResolvedValueOnce({
|
||||
baseUrl: "http://remote-ollama:11434",
|
||||
api: "ollama",
|
||||
models: [],
|
||||
});
|
||||
|
||||
const result = await provider.discovery.run({
|
||||
config: {
|
||||
models: {
|
||||
providers: {
|
||||
ollama: {
|
||||
baseUrl: "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();
|
||||
buildOllamaProviderMock.mockResolvedValueOnce({
|
||||
baseUrl: "http://127.0.0.1:11434",
|
||||
api: "ollama",
|
||||
models: [],
|
||||
});
|
||||
|
||||
const result = await provider.discovery.run({
|
||||
config: {},
|
||||
env: { NODE_ENV: "development" },
|
||||
resolveProviderApiKey: () => ({ apiKey: "ollama-local" }),
|
||||
} as never);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
provider: {
|
||||
baseUrl: "http://127.0.0.1:11434",
|
||||
api: "ollama",
|
||||
apiKey: "ollama-local",
|
||||
models: [],
|
||||
},
|
||||
});
|
||||
expect(buildOllamaProviderMock).toHaveBeenCalledWith(undefined, {
|
||||
quiet: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not mint synthetic auth for empty default-ish provider stubs", () => {
|
||||
const provider = registerProvider();
|
||||
|
||||
const auth = provider.resolveSyntheticAuth?.({
|
||||
providerConfig: {
|
||||
baseUrl: "http://127.0.0.1:11434",
|
||||
api: "ollama",
|
||||
models: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(auth).toBeUndefined();
|
||||
});
|
||||
|
||||
it("mints synthetic auth for non-default explicit ollama config", () => {
|
||||
const provider = registerProvider();
|
||||
|
||||
const auth = provider.resolveSyntheticAuth?.({
|
||||
providerConfig: {
|
||||
baseUrl: "http://remote-ollama:11434",
|
||||
api: "ollama",
|
||||
models: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(auth).toEqual({
|
||||
apiKey: "ollama-local",
|
||||
source: "models.providers.ollama (synthetic local key)",
|
||||
mode: "api-key",
|
||||
});
|
||||
});
|
||||
|
||||
it("wraps OpenAI-compatible payloads with num_ctx for Ollama compat routes", () => {
|
||||
const provider = registerProvider();
|
||||
let payloadSeen: Record<string, unknown> | undefined;
|
||||
|
||||
@@ -6,7 +6,10 @@ import {
|
||||
type ProviderAuthResult,
|
||||
type ProviderDiscoveryContext,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import {
|
||||
buildProviderReplayFamilyHooks,
|
||||
type ModelProviderConfig,
|
||||
} from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { readStringValue } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
buildOllamaProvider,
|
||||
@@ -40,6 +43,8 @@ type OllamaPluginConfig = {
|
||||
};
|
||||
};
|
||||
|
||||
type OllamaProviderLikeConfig = ModelProviderConfig;
|
||||
|
||||
function resolveOllamaDiscoveryApiKey(params: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
explicitApiKey?: string;
|
||||
@@ -55,6 +60,43 @@ function shouldSkipAmbientOllamaDiscovery(env: NodeJS.ProcessEnv): boolean {
|
||||
return Boolean(env.VITEST) || env.NODE_ENV === "test";
|
||||
}
|
||||
|
||||
function hasMeaningfulExplicitOllamaConfig(
|
||||
providerConfig: OllamaProviderLikeConfig | undefined,
|
||||
): boolean {
|
||||
if (!providerConfig) {
|
||||
return false;
|
||||
}
|
||||
if (Array.isArray(providerConfig.models) && providerConfig.models.length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (typeof providerConfig.baseUrl === "string" && providerConfig.baseUrl.trim()) {
|
||||
return resolveOllamaApiBase(providerConfig.baseUrl) !== OLLAMA_DEFAULT_BASE_URL;
|
||||
}
|
||||
if (readStringValue(providerConfig.apiKey)) {
|
||||
return true;
|
||||
}
|
||||
if (providerConfig.auth) {
|
||||
return true;
|
||||
}
|
||||
if (typeof providerConfig.authHeader === "boolean") {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
providerConfig.headers &&
|
||||
typeof providerConfig.headers === "object" &&
|
||||
Object.keys(providerConfig.headers).length > 0
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (providerConfig.request) {
|
||||
return true;
|
||||
}
|
||||
if (typeof providerConfig.injectNumCtxForOpenAICompat === "boolean") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "ollama",
|
||||
name: "Ollama Provider",
|
||||
@@ -113,12 +155,17 @@ export default definePluginEntry({
|
||||
run: async (ctx: ProviderDiscoveryContext) => {
|
||||
const explicit = ctx.config.models?.providers?.ollama;
|
||||
const hasExplicitModels = Array.isArray(explicit?.models) && explicit.models.length > 0;
|
||||
const hasMeaningfulExplicitConfig = hasMeaningfulExplicitOllamaConfig(explicit);
|
||||
const discoveryEnabled =
|
||||
pluginConfig.discovery?.enabled ?? ctx.config.models?.ollamaDiscovery?.enabled;
|
||||
if (!hasExplicitModels && discoveryEnabled === false) {
|
||||
return null;
|
||||
}
|
||||
const ollamaKey = ctx.resolveProviderApiKey(PROVIDER_ID).apiKey;
|
||||
const hasRealOllamaKey =
|
||||
typeof ollamaKey === "string" &&
|
||||
ollamaKey.trim().length > 0 &&
|
||||
ollamaKey.trim() !== DEFAULT_API_KEY;
|
||||
const explicitApiKey = readStringValue(explicit?.apiKey);
|
||||
if (hasExplicitModels && explicit) {
|
||||
return {
|
||||
@@ -137,12 +184,16 @@ export default definePluginEntry({
|
||||
},
|
||||
};
|
||||
}
|
||||
if (!ollamaKey && !explicit && shouldSkipAmbientOllamaDiscovery(ctx.env)) {
|
||||
if (
|
||||
!hasRealOllamaKey &&
|
||||
!hasMeaningfulExplicitConfig &&
|
||||
shouldSkipAmbientOllamaDiscovery(ctx.env)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const provider = await buildOllamaProvider(explicit?.baseUrl, {
|
||||
quiet: !ollamaKey && !explicit,
|
||||
quiet: !hasRealOllamaKey && !hasMeaningfulExplicitConfig,
|
||||
});
|
||||
if (provider.models.length === 0 && !ollamaKey && !explicit?.apiKey) {
|
||||
return null;
|
||||
@@ -210,11 +261,7 @@ export default definePluginEntry({
|
||||
/\bollama\b.*(?:context length|too many tokens|context window)/i.test(errorMessage) ||
|
||||
/\btruncating input\b.*\btoo long\b/i.test(errorMessage),
|
||||
resolveSyntheticAuth: ({ providerConfig }) => {
|
||||
const hasApiConfig =
|
||||
Boolean(providerConfig?.api?.trim()) ||
|
||||
Boolean(providerConfig?.baseUrl?.trim()) ||
|
||||
(Array.isArray(providerConfig?.models) && providerConfig.models.length > 0);
|
||||
if (!hasApiConfig) {
|
||||
if (!hasMeaningfulExplicitOllamaConfig(providerConfig)) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -41,11 +41,18 @@ vi.mock("../plugins/provider-runtime.js", async () => {
|
||||
return undefined;
|
||||
}
|
||||
const providerConfig = params.context.providerConfig;
|
||||
const hasApiConfig =
|
||||
Boolean(providerConfig?.api?.trim()) ||
|
||||
Boolean(providerConfig?.baseUrl?.trim()) ||
|
||||
(Array.isArray(providerConfig?.models) && providerConfig.models.length > 0);
|
||||
if (!hasApiConfig) {
|
||||
const hasMeaningfulOllamaConfig =
|
||||
params.provider !== "ollama"
|
||||
? Boolean(providerConfig?.api?.trim()) ||
|
||||
Boolean(providerConfig?.baseUrl?.trim()) ||
|
||||
(Array.isArray(providerConfig?.models) && providerConfig.models.length > 0)
|
||||
: (Array.isArray(providerConfig?.models) && providerConfig.models.length > 0) ||
|
||||
Boolean(providerConfig?.api?.trim() && providerConfig.api.trim() !== "ollama") ||
|
||||
Boolean(
|
||||
providerConfig?.baseUrl?.trim() &&
|
||||
providerConfig.baseUrl.trim().replace(/\/+$/, "") !== "http://127.0.0.1:11434",
|
||||
);
|
||||
if (!hasMeaningfulOllamaConfig) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
@@ -410,6 +417,28 @@ describe("getApiKeyForModel", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("does not mint synthetic local auth for default-ish ollama stubs", async () => {
|
||||
await withEnvAsync({ OLLAMA_API_KEY: undefined }, async () => {
|
||||
await expect(
|
||||
resolveApiKeyForProvider({
|
||||
provider: "ollama",
|
||||
store: { version: 1, profiles: {} },
|
||||
cfg: {
|
||||
models: {
|
||||
providers: {
|
||||
ollama: {
|
||||
baseUrl: "http://127.0.0.1:11434",
|
||||
api: "ollama",
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow(/No API key found for provider "ollama"/);
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers explicit OLLAMA_API_KEY over synthetic local key", async () => {
|
||||
await withEnvAsync({ [envVar("OLLAMA", "API", "KEY")]: "env-ollama-key" }, async () => {
|
||||
// pragma: allowlist secret
|
||||
|
||||
@@ -95,11 +95,14 @@ vi.mock("../plugins/provider-runtime.js", async () => {
|
||||
return undefined;
|
||||
}
|
||||
const providerConfig = params.context.providerConfig;
|
||||
const hasApiConfig =
|
||||
Boolean(providerConfig?.api?.trim()) ||
|
||||
Boolean(providerConfig?.baseUrl?.trim()) ||
|
||||
(Array.isArray(providerConfig?.models) && providerConfig.models.length > 0);
|
||||
if (!hasApiConfig) {
|
||||
const hasMeaningfulOllamaConfig =
|
||||
(Array.isArray(providerConfig?.models) && providerConfig.models.length > 0) ||
|
||||
Boolean(providerConfig?.api?.trim() && providerConfig.api.trim() !== "ollama") ||
|
||||
Boolean(
|
||||
providerConfig?.baseUrl?.trim() &&
|
||||
providerConfig.baseUrl.trim().replace(/\/+$/, "") !== "http://127.0.0.1:11434",
|
||||
);
|
||||
if (!hasMeaningfulOllamaConfig) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -138,7 +138,7 @@ describe("Ollama auto-discovery", () => {
|
||||
await runOllamaCatalog({
|
||||
explicitProviders: {
|
||||
ollama: {
|
||||
baseUrl: "http://127.0.0.1:11434/v1",
|
||||
baseUrl: "http://gpu-node-server:11434/v1",
|
||||
api: "openai-completions",
|
||||
models: [],
|
||||
},
|
||||
|
||||
@@ -416,6 +416,42 @@ export function describeOllamaProviderDiscoveryContract() {
|
||||
).resolves.toBeNull();
|
||||
expect(buildOllamaProviderMock).toHaveBeenCalledWith(undefined, { quiet: true });
|
||||
});
|
||||
|
||||
it("keeps empty default-ish provider stubs on the quiet ambient path", async () => {
|
||||
buildOllamaProviderMock.mockResolvedValueOnce({
|
||||
baseUrl: "http://127.0.0.1:11434",
|
||||
api: "ollama",
|
||||
models: [],
|
||||
});
|
||||
|
||||
await expect(
|
||||
runCatalog(state, {
|
||||
provider: state.ollamaProvider!,
|
||||
config: {
|
||||
models: {
|
||||
providers: {
|
||||
ollama: {
|
||||
baseUrl: "http://127.0.0.1:11434",
|
||||
api: "ollama",
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
resolveProviderApiKey: () => ({ apiKey: undefined }),
|
||||
resolveProviderAuth: () => ({
|
||||
apiKey: undefined,
|
||||
discoveryApiKey: undefined,
|
||||
mode: "none",
|
||||
source: "none",
|
||||
}),
|
||||
}),
|
||||
).resolves.toBeNull();
|
||||
expect(buildOllamaProviderMock).toHaveBeenCalledWith("http://127.0.0.1:11434", {
|
||||
quiet: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user