Skip to content

Commit 670bc6c

Browse files
committed
fix(ollama): allow Orb host local auth
1 parent 6ccca4a commit 670bc6c

6 files changed

Lines changed: 71 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai
1212

1313
### Fixes
1414

15+
- Providers/Ollama: treat Orb VM host aliases as local Ollama endpoints so `ollama-local` marker auth works when OpenClaw runs inside Orb and Ollama runs on the host. Fixes #84875.
1516
- Agents: cap heartbeat model bleed context hints by the stored session window when runtime model metadata is unavailable, so overflow recovery advice does not suggest a larger window than the active session actually has.
1617
- Memory/search: stop recall tracking from writing dreaming side-effect artifacts when `dreaming.enabled=false`, while preserving normal search results. Fixes #84436. (#84444) Thanks @NianJiuZst.
1718
- Diffs: render viewer toolbar icons from a closed icon-name map instead of HTML strings, removing the toolbar icon XSS sink. (#83955) Thanks @tanshanshan.

extensions/ollama/index.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,29 @@ describe("ollama plugin", () => {
572572
expect(buildOllamaProviderMock).not.toHaveBeenCalled();
573573
});
574574

575+
it("skips implicit localhost discovery when a custom Orb host Ollama provider is configured", async () => {
576+
const provider = registerProvider();
577+
578+
const result = await provider.catalog.run({
579+
config: {
580+
models: {
581+
providers: {
582+
"ollama-orb": {
583+
api: "ollama",
584+
baseUrl: "http://host.orb.internal:11434",
585+
models: [{ id: "qwen3.5:27b", name: "Qwen 3.5 27B" }],
586+
},
587+
},
588+
},
589+
},
590+
env: { NODE_ENV: "development", OLLAMA_API_KEY: "ollama-live" },
591+
resolveProviderApiKey: () => ({ apiKey: "ollama-live" }),
592+
} as never);
593+
594+
expect(result).toBeNull();
595+
expect(buildOllamaProviderMock).not.toHaveBeenCalled();
596+
});
597+
575598
it("treats custom 127/8 Ollama providers as loopback for implicit discovery", async () => {
576599
const provider = registerProvider();
577600
buildOllamaProviderMock.mockResolvedValueOnce({

extensions/ollama/src/discovery-shared.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ describe("isLocalOllamaBaseUrl", () => {
1515
"http://192.168.1.100:11434",
1616
"http://gpu-node-1:11434",
1717
"http://mac-studio.local:11434",
18+
"http://host.orb.internal:11434",
1819
"http://[fd00::1]:11434",
1920
"http://[fe90::1]:11434",
2021
])("classifies %s as local", (baseUrl) => {

extensions/ollama/src/discovery-shared.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,15 @@ function shouldSkipAmbientOllamaDiscovery(env: NodeJS.ProcessEnv): boolean {
7070
return Boolean(env.VITEST) || env.NODE_ENV === "test";
7171
}
7272

73-
const LOCAL_OLLAMA_HOSTNAMES = new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1", "::"]);
73+
const LOCAL_OLLAMA_HOSTNAMES = new Set([
74+
"localhost",
75+
"127.0.0.1",
76+
"0.0.0.0",
77+
"::1",
78+
"::",
79+
"host.orb.internal",
80+
]);
81+
const LOOPBACK_OLLAMA_HOSTNAMES = new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1", "::"]);
7482

7583
function isIpv4Loopback(host: string): boolean {
7684
if (!/^\d+\.\d+\.\d+\.\d+$/.test(host)) {
@@ -137,7 +145,7 @@ function isLoopbackOllamaBaseUrl(baseUrl: string | undefined | null): boolean {
137145
if (host.startsWith("[") && host.endsWith("]")) {
138146
host = host.slice(1, -1);
139147
}
140-
return LOCAL_OLLAMA_HOSTNAMES.has(host) || isIpv4Loopback(host);
148+
return LOOPBACK_OLLAMA_HOSTNAMES.has(host) || isIpv4Loopback(host);
141149
}
142150

143151
function hasExplicitRemoteOllamaApiProvider(

src/agents/model-auth.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,41 @@ describe("resolveApiKeyForProvider – synthetic local auth for custom providers
11111111
});
11121112
});
11131113

1114+
it("accepts ollama-local marker auth for Orb host aliases", async () => {
1115+
const auth = await resolveApiKeyForProvider({
1116+
provider: "ollama",
1117+
cfg: {
1118+
models: {
1119+
providers: {
1120+
ollama: {
1121+
baseUrl: "http://host.orb.internal:11434",
1122+
api: "ollama",
1123+
apiKey: "ollama-local",
1124+
models: [
1125+
{
1126+
id: "qwen3.5:27b",
1127+
name: "Qwen 3.5 27B",
1128+
reasoning: false,
1129+
input: ["text"],
1130+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1131+
contextWindow: 262144,
1132+
maxTokens: 8192,
1133+
},
1134+
],
1135+
},
1136+
},
1137+
},
1138+
},
1139+
store: { version: 1, profiles: {} },
1140+
});
1141+
1142+
expectAuthFields(auth, {
1143+
apiKey: "ollama-local",
1144+
source: "models.json (local marker)",
1145+
mode: "api-key",
1146+
});
1147+
});
1148+
11141149
it("does not accept non-secret local markers for remote custom providers", async () => {
11151150
await expect(
11161151
resolveApiKeyForProvider({

src/agents/model-auth.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ function isLocalBaseUrl(baseUrl: string): boolean {
267267
host === "::1" ||
268268
host === "::ffff:7f00:1" ||
269269
host === "::ffff:127.0.0.1" ||
270+
host === "host.orb.internal" ||
270271
host.endsWith(".local") ||
271272
isPrivateIpv4Host(host)
272273
);

0 commit comments

Comments
 (0)