"use strict"; const test = require("node:test"); const assert = require("node:assert/strict"); const childProcess = require("node:child_process"); const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const { HoloLakeAiGateway, HoloLakeKnowledgeProvider, } = require("./hololake-capabilities"); function createBareKnowledgeRepository() { const root = fs.mkdtempSync(path.join(os.tmpdir(), "hololake-knowledge-")); const work = path.join(root, "work"); const bare = path.join(root, "knowledge.git"); fs.mkdirSync(work); childProcess.execFileSync("git", ["init", "-b", "main"], { cwd: work }); childProcess.execFileSync("git", ["config", "user.email", "test@example.invalid"], { cwd: work }); childProcess.execFileSync("git", ["config", "user.name", "HoloLake Test"], { cwd: work }); fs.writeFileSync(path.join(work, "INDEX.md"), "# HoloLake\n"); childProcess.execFileSync("git", ["add", "INDEX.md"], { cwd: work }); childProcess.execFileSync("git", ["commit", "-m", "knowledge baseline"], { cwd: work }); childProcess.execFileSync("git", ["clone", "--bare", work, bare]); return { root, bare }; } test("knowledge provider exposes only the fixed main commit and archive", () => { const repository = createBareKnowledgeRepository(); const provider = new HoloLakeKnowledgeProvider({ repositoryId: "bingshuo/hololake-knowledge-base", repositoryPath: repository.bare, maxArchiveBytes: 2 * 1024 * 1024, }); const manifest = provider.manifest(); assert.equal( manifest.repository, "bingshuo/hololake-knowledge-base", ); assert.match(manifest.commit, /^[0-9a-f]{40}$/); assert.equal(manifest.ref, "refs/heads/main"); const archive = provider.archive(manifest.commit); assert.equal(archive.commit, manifest.commit); assert.equal(archive.content_type, "application/zip"); assert.equal(archive.body.subarray(0, 2).toString("ascii"), "PK"); assert.equal(archive.sha256.length, 64); assert.throws( () => provider.archive("0000000000000000000000000000000000000000"), /knowledge_commit_not_current/, ); }); test("knowledge provider atomically writes Markdown and returns a verifiable commit receipt", () => { const repository = createBareKnowledgeRepository(); const provider = new HoloLakeKnowledgeProvider({ repositoryId: "bingshuo/hololake-knowledge-base", repositoryPath: repository.bare, }); const base = provider.manifest().commit; const content = "# 手机知识页\n\n由 HoloLake Agent 写入。\n"; const receipt = provider.writePage({ path: "个人知识/手机知识页.md", content, baseCommit: base, }); assert.equal(receipt.schema, "guanghu.hololake-knowledge-write-receipt/v1"); assert.equal(receipt.repository, "bingshuo/hololake-knowledge-base"); assert.equal(receipt.path, "个人知识/手机知识页.md"); assert.equal(receipt.base_commit, base); assert.match(receipt.commit, /^[0-9a-f]{40}$/); assert.notEqual(receipt.commit, base); assert.equal( receipt.content_sha256, require("node:crypto").createHash("sha256").update(content).digest("hex"), ); assert.equal(provider.manifest().commit, receipt.commit); assert.equal( childProcess.execFileSync( "git", [`--git-dir=${repository.bare}`, "show", `${receipt.commit}:个人知识/手机知识页.md`], { encoding: "utf8" }, ), content, ); assert.doesNotMatch(JSON.stringify(receipt), /由 HoloLake Agent 写入/); }); test("knowledge provider rejects secret pages, unsafe paths, oversized content, and stale commits", () => { const repository = createBareKnowledgeRepository(); const provider = new HoloLakeKnowledgeProvider({ repositoryId: "bingshuo/hololake-knowledge-base", repositoryPath: repository.bare, }); const base = provider.manifest().commit; for (const invalidPath of [ "本地密钥/openai.md", "../outside.md", "notes/plain.txt", "/absolute.md", "notes//empty.md", ]) { assert.throws( () => provider.writePage({ path: invalidPath, content: "# blocked\n", baseCommit: base, }), /knowledge_page_path_invalid|knowledge_secret_page_forbidden/, ); } assert.throws( () => provider.writePage({ path: "notes/large.md", content: "x".repeat(1024 * 1024 + 1), baseCommit: base, }), /knowledge_page_too_large/, ); const first = provider.writePage({ path: "notes/first.md", content: "# first\n", baseCommit: base, }); assert.match(first.commit, /^[0-9a-f]{40}$/); assert.throws( () => provider.writePage({ path: "notes/stale.md", content: "# stale\n", baseCommit: base, }), /knowledge_commit_conflict/, ); assert.equal(provider.manifest().commit, first.commit); }); test("AI gateway accepts only registered provider models and never leaks the key", async () => { const requests = []; const gateway = new HoloLakeAiGateway({ providers: { default: { baseUrl: "https://models.example.invalid/v1", apiKey: "server-secret-key", models: ["gpt-test"], }, }, fetchImpl: async (url, options) => { requests.push({ url, options }); return new Response(JSON.stringify({ id: "response-1", choices: [{ message: { role: "assistant", content: "ok" } }], usage: { total_tokens: 3 }, }), { status: 200, headers: { "content-type": "application/json" }, }); }, }); const result = await gateway.execute({ provider: "default", model: "gpt-test", messages: [{ role: "user", content: "hello" }], max_tokens: 32, }); assert.equal(result.ok, true); assert.equal(result.response.choices[0].message.content, "ok"); assert.equal(result.receipt.provider, "default"); assert.equal(requests.length, 1); assert.equal( requests[0].options.headers.authorization, "Bearer server-secret-key", ); assert.doesNotMatch(JSON.stringify(result), /server-secret-key/); const catalog = gateway.catalog(); assert.deepEqual(catalog.providers[0].models, ["gpt-test"]); assert.doesNotMatch(JSON.stringify(catalog), /server-secret-key|models\\.example/); await assert.rejects( () => gateway.execute({ provider: "default", model: "not-registered", messages: [{ role: "user", content: "hello" }], }), /ai_model_not_registered/, ); await assert.rejects( () => gateway.execute({ provider: "https://attacker.invalid/v1", model: "gpt-test", messages: [{ role: "user", content: "hello" }], }), /ai_provider_not_registered/, ); assert.equal(requests.length, 1); }); test("AI gateway forwards bounded tool contracts and tool receipts without exposing server secrets", async () => { const requests = []; const gateway = new HoloLakeAiGateway({ providers: { default: { baseUrl: "https://models.example.invalid/v1", apiKey: "server-secret-key", models: ["gpt-test"], }, }, fetchImpl: async (_url, options) => { requests.push(JSON.parse(options.body)); return new Response(JSON.stringify({ choices: [{ message: { role: "assistant", content: "saved" } }], }), { status: 200 }); }, }); const result = await gateway.execute({ provider: "default", model: "gpt-test", messages: [ { role: "user", content: "save this" }, { role: "assistant", content: null, tool_calls: [{ id: "call-1", type: "function", function: { name: "create_note", arguments: "{\"path\":\"notes/a.md\"}" }, }], }, { role: "tool", tool_call_id: "call-1", content: "commit=abc" }, ], tools: [{ type: "function", function: { name: "create_note", description: "Create one Markdown page.", parameters: { type: "object", properties: { path: { type: "string" } }, required: ["path"], }, }, }], tool_choice: "auto", }); assert.equal(result.response.choices[0].message.content, "saved"); assert.equal(requests[0].tools[0].function.name, "create_note"); assert.equal(requests[0].messages[2].role, "tool"); assert.equal(requests[0].tool_choice, "auto"); assert.doesNotMatch(JSON.stringify(result), /server-secret-key/); await assert.rejects( () => gateway.execute({ provider: "default", model: "gpt-test", messages: [{ role: "tool", tool_call_id: "../bad", content: "x" }], }), /ai_message_invalid/, ); }); test("AI gateway bounds message shape, count, text, and requested output", async () => { const gateway = new HoloLakeAiGateway({ providers: { default: { baseUrl: "https://models.example.invalid/v1", apiKey: "server-secret-key", models: ["gpt-test"], }, }, fetchImpl: async () => new Response("{}", { status: 200 }), maxMessages: 2, maxInputCharacters: 10, maxOutputTokens: 64, }); await assert.rejects( () => gateway.execute({ provider: "default", model: "gpt-test", messages: [{ role: "user", content: "12345678901" }], }), /ai_input_too_large/, ); await assert.rejects( () => gateway.execute({ provider: "default", model: "gpt-test", messages: [{ role: "tool", content: "hello" }], }), /ai_message_invalid/, ); await assert.rejects( () => gateway.execute({ provider: "default", model: "gpt-test", messages: [{ role: "user", content: "hello" }], max_tokens: 65, }), /ai_output_limit_exceeded/, ); });