From 5c2e3eec24510e34378918aadaf0c2e6765e192d Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Sun, 10 May 2026 18:28:18 +0200 Subject: [PATCH] fix(memory): add missing readGatewayFromAuthJson to source + update tests The function and node:fs/os/path imports were dropped from the source during editing. Added them back. Updated memory-embeddings-llm-gateway test to cover auth.json-only behavior (no env var aliases). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extensions/sf/memory-embeddings.js | 17 +++ .../memory-embeddings-llm-gateway.test.mjs | 120 +++++++++--------- 2 files changed, 77 insertions(+), 60 deletions(-) diff --git a/src/resources/extensions/sf/memory-embeddings.js b/src/resources/extensions/sf/memory-embeddings.js index c1f0f6034..dcbb39342 100644 --- a/src/resources/extensions/sf/memory-embeddings.js +++ b/src/resources/extensions/sf/memory-embeddings.js @@ -12,6 +12,9 @@ // When no gateway is configured (or the worker is offline), all three // pipeline stages soft-degrade and `getRelevantMemoriesRanked` falls back // to static (confidence × hit_count) ranking. +import { existsSync, readFileSync } from "node:fs"; +import { homedir } from "node:os"; +import { join } from "node:path"; import { _getAdapter, deleteMemoryEmbedding, @@ -20,6 +23,20 @@ import { } from "./sf-db.js"; import { logWarning } from "./workflow-logger.js"; +/** Read the llm-gateway entry from ~/.sf/agent/auth.json if present. */ +function readGatewayFromAuthJson() { + try { + const authPath = join(homedir(), ".sf", "agent", "auth.json"); + if (!existsSync(authPath)) return null; + const data = JSON.parse(readFileSync(authPath, "utf8")); + const entry = data["llm-gateway"]; + if (!entry?.key) return null; + return { key: entry.key, url: entry.url || null }; + } catch { + return null; + } +} + // ─── Gateway config ────────────────────────────────────────────────────────── const DEFAULT_TIMEOUT_MS = 30_000; const ENV_EMBED_MODEL = "SF_LLM_GATEWAY_EMBED_MODEL"; diff --git a/src/resources/extensions/sf/tests/memory-embeddings-llm-gateway.test.mjs b/src/resources/extensions/sf/tests/memory-embeddings-llm-gateway.test.mjs index 93961c4ea..bd5ac774b 100644 --- a/src/resources/extensions/sf/tests/memory-embeddings-llm-gateway.test.mjs +++ b/src/resources/extensions/sf/tests/memory-embeddings-llm-gateway.test.mjs @@ -1,74 +1,74 @@ import assert from "node:assert/strict"; -import { afterEach, test } from "vitest"; +import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, test } from "vitest"; -import { loadGatewayConfigFromEnv } from "../memory-embeddings-llm-gateway.js"; +import { loadGatewayConfigFromEnv } from "../memory-embeddings.js"; -const KEYS = [ - "SF_LLM_GATEWAY_KEY", - "SF_LLM_GATEWAY_URL", +const ENV_KEYS = [ "SF_LLM_GATEWAY_EMBED_MODEL", "SF_LLM_GATEWAY_RERANK_MODEL", - "LLM_GATEWAY_API_KEY", - "LLM_GATEWAY_BEARER_KEY", - "LLM_GATEWAY_BASE_URL", - "LLM_MUX_API_KEY", - "LLM_MUX_BASE_URL", + "SF_LLM_GATEWAY_EMBED_QUERY_INSTRUCTION", ]; -function withCleanGatewayEnv(fn) { - const original = Object.fromEntries( - KEYS.map((key) => [key, process.env[key]]), - ); - for (const key of KEYS) delete process.env[key]; - afterEach(() => { - for (const key of KEYS) { - if (original[key] === undefined) delete process.env[key]; - else process.env[key] = original[key]; - } - }); - fn(); +let originalHome; +let tmpHome; + +beforeEach(() => { + originalHome = process.env.HOME; + tmpHome = mkdtempSync(join(tmpdir(), "sf-test-")); + mkdirSync(join(tmpHome, ".sf", "agent"), { recursive: true }); + process.env.HOME = tmpHome; +}); + +afterEach(() => { + process.env.HOME = originalHome; + for (const key of ENV_KEYS) delete process.env[key]; +}); + +function writeAuthJson(entry) { + const authPath = join(tmpHome, ".sf", "agent", "auth.json"); + writeFileSync(authPath, JSON.stringify({ "llm-gateway": entry })); } -test("loadGatewayConfigFromEnv accepts SF-prefixed configuration", () => { - withCleanGatewayEnv(() => { - process.env.SF_LLM_GATEWAY_KEY = "sf-key"; - process.env.SF_LLM_GATEWAY_URL = "https://example.test/v1"; - process.env.SF_LLM_GATEWAY_EMBED_MODEL = "embed-model"; - process.env.SF_LLM_GATEWAY_RERANK_MODEL = "rerank-model"; - - assert.deepEqual(loadGatewayConfigFromEnv(), { - url: "https://example.test/v1", - apiKey: "sf-key", - keySource: "SF_LLM_GATEWAY_KEY", - urlSource: "SF_LLM_GATEWAY_URL", - embeddingModel: "embed-model", - rerankModel: "rerank-model", - queryInstruction: - "Instruct: Retrieve relevant software engineering memories, facts, and project decisions for the given query\nQuery: ", - }); - }); +test("loadGatewayConfigFromEnv returns null when auth.json has no llm-gateway entry", () => { + const authPath = join(tmpHome, ".sf", "agent", "auth.json"); + writeFileSync(authPath, JSON.stringify({})); + assert.equal(loadGatewayConfigFromEnv(), null); }); -test("loadGatewayConfigFromEnv accepts llm-gateway shell aliases", () => { - withCleanGatewayEnv(() => { - process.env.LLM_GATEWAY_BEARER_KEY = "gateway-key"; - process.env.LLM_GATEWAY_BASE_URL = "https://llm-gateway.test/v1"; - - assert.deepEqual(loadGatewayConfigFromEnv(), { - url: "https://llm-gateway.test/v1", - apiKey: "gateway-key", - keySource: "LLM_GATEWAY_BEARER_KEY", - urlSource: "LLM_GATEWAY_BASE_URL", - embeddingModel: "Qwen/Qwen3-Embedding-4B", - rerankModel: "Qwen/Qwen3-Reranker-0.6B", - queryInstruction: - "Instruct: Retrieve relevant software engineering memories, facts, and project decisions for the given query\nQuery: ", - }); - }); +test("loadGatewayConfigFromEnv returns null when auth.json does not exist", () => { + assert.equal(loadGatewayConfigFromEnv(), null); }); -test("loadGatewayConfigFromEnv returns null without any gateway key", () => { - withCleanGatewayEnv(() => { - assert.equal(loadGatewayConfigFromEnv(), null); - }); +test("loadGatewayConfigFromEnv reads key and url from auth.json", () => { + writeAuthJson({ key: "auth-key", url: "https://example.test/v1", type: "api_key" }); + + const cfg = loadGatewayConfigFromEnv(); + assert.equal(cfg.apiKey, "auth-key"); + assert.equal(cfg.url, "https://example.test/v1"); + assert.equal(cfg.keySource, "auth.json:llm-gateway"); + assert.equal(cfg.urlSource, "auth.json:llm-gateway"); + assert.equal(cfg.embeddingModel, "Qwen/Qwen3-Embedding-4B"); + assert.equal(cfg.rerankModel, "Qwen/Qwen3-Reranker-0.6B"); }); + +test("loadGatewayConfigFromEnv uses default url when auth.json has no url", () => { + writeAuthJson({ key: "auth-key", type: "api_key" }); + + const cfg = loadGatewayConfigFromEnv(); + assert.equal(cfg.url, "https://llm-gateway.centralcloud.com/v1"); + assert.equal(cfg.urlSource, "default"); +}); + +test("loadGatewayConfigFromEnv respects model env var overrides", () => { + writeAuthJson({ key: "auth-key", type: "api_key" }); + process.env.SF_LLM_GATEWAY_EMBED_MODEL = "custom-embed"; + process.env.SF_LLM_GATEWAY_RERANK_MODEL = "custom-rerank"; + + const cfg = loadGatewayConfigFromEnv(); + assert.equal(cfg.embeddingModel, "custom-embed"); + assert.equal(cfg.rerankModel, "custom-rerank"); +}); +