feat(hololake): add mobile server account and capability proxy

This commit is contained in:
冰朔 2026-08-02 21:49:53 +08:00
commit 19bd38c6e3
11 changed files with 1436 additions and 1 deletions

View file

@ -0,0 +1,158 @@
"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("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 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/,
);
});