






























@@ -7,8 +7,13 @@ import {
77type ProviderAuthMethodNonInteractiveContext,
88type ProviderAuthResult,
99type ProviderDiscoveryContext,
10+type ProviderRuntimeModel,
1011} from "openclaw/plugin-sdk/plugin-entry";
1112import { buildApiKeyCredential } from "openclaw/plugin-sdk/provider-auth";
13+import type {
14+ModelDefinitionConfig,
15+ModelProviderConfig,
16+} from "openclaw/plugin-sdk/provider-model-shared";
1217import {
1318buildOpenAICompatibleReplayPolicy,
1419OPENAI_COMPATIBLE_REPLAY_HOOKS,
@@ -57,6 +62,44 @@ function usesOllamaOpenAICompatTransport(model: {
5762);
5863}
596465+const dynamicModelCache = new Map<string, ProviderRuntimeModel[]>();
66+67+function buildDynamicCacheKey(provider: string, baseUrl: string | undefined): string {
68+return `${provider}\0${baseUrl ?? ""}`;
69+}
70+71+function hasOllamaDiscoverySignal(providerConfig: ModelProviderConfig | undefined): boolean {
72+return (
73+Boolean(process.env.OLLAMA_API_KEY?.trim()) ||
74+shouldUseSyntheticOllamaAuth(providerConfig) ||
75+Boolean(providerConfig?.apiKey)
76+);
77+}
78+79+function toDynamicOllamaModel(params: {
80+provider: string;
81+providerConfig: ModelProviderConfig;
82+model: ModelDefinitionConfig;
83+}): ProviderRuntimeModel {
84+const input = (params.model.input ?? ["text"]).filter(
85+(value): value is "text" | "image" => value === "text" || value === "image",
86+);
87+return {
88+id: params.model.id,
89+name: params.model.name ?? params.model.id,
90+provider: params.provider,
91+api: "ollama",
92+baseUrl: readProviderBaseUrl(params.providerConfig) ?? "",
93+reasoning: params.model.reasoning ?? false,
94+input: input.length > 0 ? input : ["text"],
95+cost: params.model.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
96+contextWindow: params.model.contextWindow ?? 8192,
97+maxTokens: params.model.maxTokens ?? 8192,
98+ ...(params.model.compat ? { compat: params.model.compat as never } : {}),
99+ ...(params.model.params ? { params: params.model.params } : {}),
100+};
101+}
102+60103export default definePluginEntry({
61104id: "ollama",
62105name: "Ollama Provider",
@@ -215,6 +258,36 @@ export default definePluginEntry({
215258},
216259shouldDeferSyntheticProfileAuth: ({ resolvedApiKey }) =>
217260resolvedApiKey?.trim() === OLLAMA_DEFAULT_API_KEY,
261+prepareDynamicModel: async (ctx) => {
262+const providerConfig = resolveConfiguredOllamaProviderConfig({
263+config: ctx.config,
264+providerId: ctx.provider,
265+});
266+if (!hasOllamaDiscoverySignal(providerConfig)) {
267+return;
268+}
269+const baseUrl = readProviderBaseUrl(providerConfig);
270+const provider = await buildOllamaProvider(baseUrl, { quiet: true });
271+dynamicModelCache.set(
272+buildDynamicCacheKey(ctx.provider, baseUrl),
273+(provider.models ?? []).map((model) =>
274+toDynamicOllamaModel({
275+provider: ctx.provider,
276+providerConfig: provider,
277+ model,
278+}),
279+),
280+);
281+},
282+resolveDynamicModel: (ctx) => {
283+const providerConfig = resolveConfiguredOllamaProviderConfig({
284+config: ctx.config,
285+providerId: ctx.provider,
286+});
287+return dynamicModelCache
288+.get(buildDynamicCacheKey(ctx.provider, readProviderBaseUrl(providerConfig)))
289+?.find((model) => model.id === ctx.modelId);
290+},
218291buildUnknownModelHint: () =>
219292"Ollama requires authentication to be registered as a provider. " +
220293'Set OLLAMA_API_KEY="ollama-local" (any value works) or run "openclaw configure". ' +
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。