"use strict"; const childProcess = require("node:child_process"); const crypto = require("node:crypto"); const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const MAX_KNOWLEDGE_PAGE_BYTES = 1024 * 1024; class HoloLakeKnowledgeProvider { constructor(options = {}) { this.repositoryId = String(options.repositoryId || ""); this.repositoryPath = String(options.repositoryPath || ""); this.ref = "refs/heads/main"; this.maxArchiveBytes = Math.max( 1024 * 1024, Number(options.maxArchiveBytes || 128 * 1024 * 1024), ); if (!/^[a-z0-9._-]+\/[a-z0-9._-]+$/.test(this.repositoryId)) { throw new Error("knowledge_repository_id_invalid"); } if (!this.repositoryPath || !fs.existsSync(this.repositoryPath)) { throw new Error("knowledge_repository_unavailable"); } } manifest() { const commit = this.git(["rev-parse", "--verify", `${this.ref}^{commit}`]) .toString("utf8") .trim(); if (!/^[0-9a-f]{40}$/.test(commit)) { throw new Error("knowledge_commit_invalid"); } const committedAt = Number( this.git(["show", "-s", "--format=%ct", commit]).toString("utf8").trim(), ); return { schema: "guanghu.hololake-knowledge-manifest/v1", repository: this.repositoryId, ref: this.ref, commit, committed_at: committedAt, archive_url: `/api/hololake/knowledge/archive?commit=${commit}`, }; } archive(commit) { const manifest = this.manifest(); if (!safeEqual(String(commit || ""), manifest.commit)) { throw new Error("knowledge_commit_not_current"); } const body = this.git( ["archive", "--format=zip", manifest.commit], this.maxArchiveBytes + 1, ); if (body.length > this.maxArchiveBytes) { throw new Error("knowledge_archive_too_large"); } return { schema: "guanghu.hololake-knowledge-archive/v1", repository: this.repositoryId, commit: manifest.commit, sha256: crypto.createHash("sha256").update(body).digest("hex"), content_type: "application/zip", body, }; } writePage(request = {}) { const pagePath = validateKnowledgePagePath(request.path); if (typeof request.content !== "string") { throw new Error("knowledge_page_content_invalid"); } const content = Buffer.from(request.content, "utf8"); if (content.length > MAX_KNOWLEDGE_PAGE_BYTES) { throw new Error("knowledge_page_too_large"); } if (content.includes(0)) { throw new Error("knowledge_page_content_invalid"); } const current = this.manifest().commit; const baseCommit = String(request.baseCommit || ""); if (!/^[0-9a-f]{40}$/.test(baseCommit) || !safeEqual(baseCommit, current)) { throw new Error("knowledge_commit_conflict"); } const temporary = fs.mkdtempSync( path.join(os.tmpdir(), "hololake-knowledge-index-"), ); const indexFile = path.join(temporary, "index"); const gitEnv = { ...process.env, GIT_INDEX_FILE: indexFile, GIT_AUTHOR_NAME: "HoloLake Knowledge", GIT_AUTHOR_EMAIL: "knowledge@guanghulake.invalid", GIT_COMMITTER_NAME: "HoloLake Knowledge", GIT_COMMITTER_EMAIL: "knowledge@guanghulake.invalid", }; try { this.git(["read-tree", current], 1024 * 1024, { env: gitEnv }); const blob = this.git( ["hash-object", "-w", "--stdin"], MAX_KNOWLEDGE_PAGE_BYTES + 1024, { env: gitEnv, input: content }, ).toString("utf8").trim(); if (!/^[0-9a-f]{40}$/.test(blob)) { throw new Error("knowledge_blob_invalid"); } this.git( ["update-index", "--add", "--cacheinfo", "100644", blob, pagePath], 1024 * 1024, { env: gitEnv }, ); const tree = this.git(["write-tree"], 1024 * 1024, { env: gitEnv }) .toString("utf8") .trim(); if (!/^[0-9a-f]{40}$/.test(tree)) { throw new Error("knowledge_tree_invalid"); } const nextCommit = this.git( [ "commit-tree", tree, "-p", current, "-m", `HoloLake knowledge: ${pagePath}`, ], 1024 * 1024, { env: gitEnv }, ).toString("utf8").trim(); if (!/^[0-9a-f]{40}$/.test(nextCommit)) { throw new Error("knowledge_commit_invalid"); } try { this.git( ["update-ref", this.ref, nextCommit, current], 1024 * 1024, { env: gitEnv }, ); } catch { throw new Error("knowledge_commit_conflict"); } return { schema: "guanghu.hololake-knowledge-write-receipt/v1", repository: this.repositoryId, path: pagePath, base_commit: current, commit: nextCommit, content_sha256: crypto.createHash("sha256").update(content).digest("hex"), }; } finally { fs.rmSync(temporary, { recursive: true, force: true }); } } git(args, maxBuffer = 1024 * 1024, options = {}) { return childProcess.execFileSync( "git", [`--git-dir=${this.repositoryPath}`, ...args], { encoding: "buffer", maxBuffer, stdio: [options.input === undefined ? "ignore" : "pipe", "pipe", "pipe"], env: options.env || process.env, input: options.input, }, ); } } class HoloLakeAiGateway { constructor(options = {}) { this.fetchImpl = options.fetchImpl || fetch; this.maxMessages = Math.max(1, Number(options.maxMessages || 64)); this.maxInputCharacters = Math.max( 1, Number(options.maxInputCharacters || 120_000), ); this.maxOutputTokens = Math.max( 16, Number(options.maxOutputTokens || 16_384), ); this.maxResponseBytes = Math.max( 64 * 1024, Number(options.maxResponseBytes || 2 * 1024 * 1024), ); this.providers = normalizeProviders(options.providers || {}); } catalog() { return { schema: "guanghu.hololake-ai-catalog/v1", providers: Object.entries(this.providers).map(([id, provider]) => ({ id, name: provider.name || id, models: provider.models, })), }; } async execute(request = {}) { const providerId = String(request.provider || ""); const provider = this.providers[providerId]; if (!provider) throw new Error("ai_provider_not_registered"); const model = String(request.model || ""); if (!provider.models.includes(model)) { throw new Error("ai_model_not_registered"); } const messages = validateMessages( request.messages, this.maxMessages, this.maxInputCharacters, ); const tools = validateTools(request.tools); const maxTokens = Number(request.max_tokens || 1024); if ( !Number.isInteger(maxTokens) || maxTokens < 1 || maxTokens > this.maxOutputTokens ) { throw new Error("ai_output_limit_exceeded"); } const upstreamBody = { model, messages, max_tokens: maxTokens, stream: false, }; if (tools.length > 0) { upstreamBody.tools = tools; upstreamBody.tool_choice = request.tool_choice === "none" ? "none" : "auto"; } const response = await this.fetchImpl(`${provider.baseUrl}/chat/completions`, { method: "POST", headers: { authorization: `Bearer ${provider.apiKey}`, "content-type": "application/json", }, body: JSON.stringify(upstreamBody), signal: AbortSignal.timeout(provider.timeoutMs), }); const text = await response.text(); if (Buffer.byteLength(text) > this.maxResponseBytes) { throw new Error("ai_response_too_large"); } if (!response.ok) { const error = new Error("ai_upstream_failed"); error.status = response.status; throw error; } let parsed; try { parsed = JSON.parse(text); } catch { throw new Error("ai_upstream_invalid_json"); } return { ok: true, response: parsed, receipt: { schema: "guanghu.hololake-ai-receipt/v1", state: "executed", provider: providerId, model, upstream_status: response.status, occurred_at: Date.now() / 1000, }, }; } } function loadAiProviders(file) { if (!file || !fs.existsSync(file)) return {}; const parsed = JSON.parse(fs.readFileSync(file, "utf8")); if (!parsed || parsed.schema !== "guanghu.hololake-ai-providers/v1") { throw new Error("ai_provider_registry_invalid"); } return parsed.providers || {}; } function normalizeProviders(providers) { const normalized = {}; for (const [id, value] of Object.entries(providers)) { if (!/^[a-z0-9._-]{1,64}$/.test(id) || !value) continue; const baseUrl = String(value.baseUrl || value.base_url || "").replace(/\/$/, ""); const apiKey = String(value.apiKey || value.api_key || ""); const models = Array.isArray(value.models) ? value.models.map(String).filter(model => /^[A-Za-z0-9._:/-]{1,160}$/.test(model)) : []; let parsed; try { parsed = new URL(baseUrl); } catch { continue; } if (parsed.protocol !== "https:" || !apiKey || models.length === 0) continue; normalized[id] = { baseUrl, apiKey, name: String(value.name || id).slice(0, 120), models, timeoutMs: Math.max(1_000, Number(value.timeoutMs || value.timeout_ms || 120_000)), }; } return normalized; } function validateMessages(messages, maxMessages, maxInputCharacters) { if (!Array.isArray(messages) || messages.length < 1) { throw new Error("ai_messages_required"); } if (messages.length > maxMessages) throw new Error("ai_input_too_large"); let characters = 0; const normalized = messages.map(message => { if (!message || !["system", "user", "assistant", "tool"].includes(message.role)) { throw new Error("ai_message_invalid"); } if (message.role === "tool") { if ( typeof message.content !== "string" || !/^[A-Za-z0-9._:-]{1,160}$/.test(String(message.tool_call_id || "")) ) { throw new Error("ai_message_invalid"); } characters += message.content.length; return { role: "tool", tool_call_id: message.tool_call_id, content: message.content, }; } const content = message.content === null && message.role === "assistant" ? null : message.content; if (typeof content !== "string" && content !== null) { throw new Error("ai_message_invalid"); } characters += typeof content === "string" ? content.length : 0; const normalizedMessage = { role: message.role, content }; if (message.role === "assistant" && message.tool_calls !== undefined) { normalizedMessage.tool_calls = validateToolCalls(message.tool_calls); characters += normalizedMessage.tool_calls.reduce( (total, call) => total + call.function.arguments.length, 0, ); } return normalizedMessage; }); if (characters > maxInputCharacters) throw new Error("ai_input_too_large"); return normalized; } function validateToolCalls(value) { if (!Array.isArray(value) || value.length < 1 || value.length > 32) { throw new Error("ai_message_invalid"); } return value.map(call => { if ( !call || call.type !== "function" || !/^[A-Za-z0-9._:-]{1,160}$/.test(String(call.id || "")) || !call.function || !/^[A-Za-z0-9_-]{1,80}$/.test(String(call.function.name || "")) || typeof call.function.arguments !== "string" || call.function.arguments.length > 32 * 1024 ) { throw new Error("ai_message_invalid"); } return { id: call.id, type: "function", function: { name: call.function.name, arguments: call.function.arguments, }, }; }); } function validateTools(value) { if (value === undefined || value === null) return []; if (!Array.isArray(value) || value.length > 32) { throw new Error("ai_tools_invalid"); } const encoded = JSON.stringify(value); if (Buffer.byteLength(encoded) > 128 * 1024) { throw new Error("ai_tools_invalid"); } return value.map(tool => { if ( !tool || tool.type !== "function" || !tool.function || !/^[A-Za-z0-9_-]{1,80}$/.test(String(tool.function.name || "")) || typeof tool.function.description !== "string" || tool.function.description.length > 4_000 || !tool.function.parameters || typeof tool.function.parameters !== "object" || Array.isArray(tool.function.parameters) ) { throw new Error("ai_tools_invalid"); } return { type: "function", function: { name: tool.function.name, description: tool.function.description, parameters: JSON.parse(JSON.stringify(tool.function.parameters)), }, }; }); } function safeEqual(left, right) { const a = Buffer.from(String(left)); const b = Buffer.from(String(right)); return a.length === b.length && crypto.timingSafeEqual(a, b); } function validateKnowledgePagePath(value) { const pagePath = String(value || "").normalize("NFC"); if ( pagePath.length < 1 || pagePath.length > 512 || pagePath.startsWith("/") || pagePath.endsWith("/") || pagePath.includes("\\") || /[\0-\x1f\x7f]/u.test(pagePath) || !pagePath.endsWith(".md") ) { throw new Error("knowledge_page_path_invalid"); } const components = pagePath.split("/"); if ( components.length < 1 || components.length > 16 || components.some(component => ( !component || component === "." || component === ".." || component.length > 120 || component.toLocaleLowerCase("en-US") === ".git" )) ) { throw new Error("knowledge_page_path_invalid"); } if (components.some(component => component.normalize("NFKC") === "本地密钥")) { throw new Error("knowledge_secret_page_forbidden"); } return pagePath; } module.exports = { HoloLakeAiGateway, HoloLakeKnowledgeProvider, loadAiProviders, };