Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions src/lib/inference/ollama/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,12 @@ function printProxyPortConflict(owners: { pids: number[]; descriptions: string[]
// so poll with backoff instead of the previous single 2s probe (issue #4820).
const PROXY_START_ATTEMPTS = 12;

function startOllamaAuthProxy(backendUrl?: string): boolean {
function generateProxyToken(): string {
const crypto = require("crypto");
return crypto.randomBytes(24).toString("hex");
}

function startOllamaAuthProxyWithToken(proxyToken: string, backendUrl?: string): boolean {
killStaleProxy();

// After clearing any stale NemoClaw proxy, a process still holding the port
Expand All @@ -281,11 +285,10 @@ function startOllamaAuthProxy(backendUrl?: string): boolean {
return false;
}

const proxyToken = crypto.randomBytes(24).toString("hex");
ollamaProxyToken = proxyToken;
// Don't persist yet — wait until provider is confirmed in setupInference.
// If the user backs out to a different provider, the token stays in memory
// only and is discarded.
// Don't commit the selected backend yet — wait until setupInference confirms
// the provider. A newly generated token remains in memory and is discarded
// if the user backs out.
const pid = spawnOllamaAuthProxy(proxyToken, backendUrl || `http://127.0.0.1:${OLLAMA_PORT}`);

// Poll for readiness with backoff. Three terminal outcomes:
Expand Down Expand Up @@ -325,9 +328,17 @@ function startOllamaAuthProxy(backendUrl?: string): boolean {
return false;
}

function startOllamaAuthProxy(backendUrl?: string): boolean {
// Re-onboarding the committed local Ollama route must keep the credential
// already mounted in the sandbox. A compatible custom endpoint uses the
// explicit fresh-token path below until provider selection commits it.
const proxyToken = loadPersistedProxyToken() ?? generateProxyToken();
return startOllamaAuthProxyWithToken(proxyToken, backendUrl);
Comment thread
sandl99 marked this conversation as resolved.
}

function noAuthProxy(endpointUrl: string) {
const endpoint = new URL(endpointUrl);
if (!startOllamaAuthProxy(endpoint.origin)) {
if (!startOllamaAuthProxyWithToken(generateProxyToken(), endpoint.origin)) {
restorePersistedOllamaAuthProxy();
throw new Error("Could not start the protected loopback route.");
}
Expand Down
4 changes: 2 additions & 2 deletions test/ollama-proxy-recovery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ console.log(JSON.stringify({
assert.equal(payload.proxySpawns[0].env.OLLAMA_BACKEND_PORT, "11434");
});

it("keeps a newly started Ollama backend instead of restoring a compatible backend (#7424)", () => {
it("keeps the committed token when switching from a compatible backend to Ollama (#7424)", () => {
const repoRoot = path.join(import.meta.dirname, "..");
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-ollama-proxy-switch-"));
const scriptPath = path.join(tmpDir, "provider-switch-check.js");
Expand Down Expand Up @@ -484,7 +484,7 @@ console.log(JSON.stringify({
assert.equal(payload.proxySpawns[0].token, payload.runningToken);
assert.equal(payload.proxySpawns[1].backendUrl, "http://127.0.0.1:11434");
assert.equal(payload.proxySpawns[1].token, payload.runningToken);
assert.notEqual(payload.runningToken, "compatible-token");
assert.equal(payload.runningToken, "compatible-token");
assert.deepEqual(payload.runCommands, [["kill", "4242"]]);
assert.equal(payload.persistedBackend, "http://127.0.0.1:11434");
});
Expand Down
Loading