@alucard_24/pi-codeindex 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +345 -2
- package/config.schema.json +16 -0
- package/eval/queries.jsonl +8 -0
- package/package.json +33 -3
- package/scripts/setup-gpu.sh +65 -0
- package/scripts/setup-vulkan.mjs +140 -0
- package/scripts/vulkan-sources.json +29 -0
- package/src/backend.js +120 -0
- package/src/chunker.js +276 -0
- package/src/cli.js +196 -0
- package/src/config-ui.js +35 -0
- package/src/config.js +126 -0
- package/src/doctor.js +96 -0
- package/src/embed.js +88 -0
- package/src/eval.js +72 -0
- package/src/extension.js +485 -0
- package/src/graph.js +306 -0
- package/src/indexer.js +302 -0
- package/src/model.js +71 -0
- package/src/present.js +177 -0
- package/src/probe.js +20 -0
- package/src/search.js +189 -0
- package/src/store.js +142 -0
- package/src/sync.js +106 -0
- package/src/util.js +108 -0
- package/src/vulkan.js +151 -0
package/src/util.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Utility pure: hash, git, enumerazione file, euristiche su testo/binari.
|
|
2
|
+
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
5
|
+
import fs from 'node:fs/promises';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
export function sha1(input) {
|
|
9
|
+
return createHash('sha1').update(input).digest('hex');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function short(s, n = 60) {
|
|
13
|
+
const one = String(s).replace(/\s+/g, ' ').trim();
|
|
14
|
+
return one.length > n ? `${one.slice(0, n - 1)}…` : one;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function git(cwd, args) {
|
|
18
|
+
try {
|
|
19
|
+
return execFileSync('git', args, {
|
|
20
|
+
cwd,
|
|
21
|
+
encoding: 'utf8',
|
|
22
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
23
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
24
|
+
});
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function repoRoot(cwd) {
|
|
31
|
+
const out = git(cwd, ['rev-parse', '--show-toplevel']);
|
|
32
|
+
return out ? out.trim() : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function headCommit(cwd) {
|
|
36
|
+
const out = git(cwd, ['rev-parse', 'HEAD']);
|
|
37
|
+
return out ? out.trim() : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Elenca i file tracciati + untracked non ignorati. Null se non è un repo git. */
|
|
41
|
+
export function lsFiles(root) {
|
|
42
|
+
const out = git(root, ['ls-files', '-z', '--cached', '--others', '--exclude-standard']);
|
|
43
|
+
if (out === null) return null;
|
|
44
|
+
return out.split('\0').filter(Boolean);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const IGNORED_DIRS = new Set([
|
|
48
|
+
'.git', 'node_modules', 'dist', 'build', 'out', '__pycache__', '.venv', 'venv',
|
|
49
|
+
'target', 'vendor', '.next', '.nuxt', '.openlore', '.pi', 'coverage', '.cache',
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
/** Fallback quando non è un repo git: walk con esclusioni minime. */
|
|
53
|
+
export async function walkFiles(root, { maxDepth = 32 } = {}) {
|
|
54
|
+
const acc = [];
|
|
55
|
+
async function rec(dir, depth) {
|
|
56
|
+
if (depth > maxDepth) return;
|
|
57
|
+
let entries;
|
|
58
|
+
try {
|
|
59
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
60
|
+
} catch {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
for (const e of entries) {
|
|
64
|
+
if (e.name.startsWith('.') && e.isDirectory()) continue;
|
|
65
|
+
const p = path.join(dir, e.name);
|
|
66
|
+
if (e.isDirectory()) {
|
|
67
|
+
if (IGNORED_DIRS.has(e.name)) continue;
|
|
68
|
+
await rec(p, depth + 1);
|
|
69
|
+
} else if (e.isFile()) {
|
|
70
|
+
acc.push(path.relative(root, p));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
await rec(root, 0);
|
|
75
|
+
return acc;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Byte NUL nei primi 8 KB: binario. */
|
|
79
|
+
export function isBinary(buf) {
|
|
80
|
+
const n = Math.min(buf.length, 8192);
|
|
81
|
+
for (let i = 0; i < n; i++) if (buf[i] === 0) return true;
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Riga più lunga > 2000 char: probabile bundle/minified. */
|
|
86
|
+
export function isMinified(text) {
|
|
87
|
+
if (text.length < 4096) return false;
|
|
88
|
+
let run = 0;
|
|
89
|
+
for (let i = 0; i < text.length; i++) {
|
|
90
|
+
if (text.charCodeAt(i) === 10) run = 0;
|
|
91
|
+
else if (++run > 2000) return true;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function fmtDuration(ms) {
|
|
97
|
+
if (ms < 1000) return `${Math.round(ms)} ms`;
|
|
98
|
+
const s = ms / 1000;
|
|
99
|
+
if (s < 60) return `${s.toFixed(1)} s`;
|
|
100
|
+
return `${Math.floor(s / 60)}m${String(Math.round(s % 60)).padStart(2, '0')}s`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function fmtBytes(n) {
|
|
104
|
+
if (n < 1024) return `${n} B`;
|
|
105
|
+
if (n < 1024 ** 2) return `${(n / 1024).toFixed(0)} KB`;
|
|
106
|
+
if (n < 1024 ** 3) return `${(n / 1024 ** 2).toFixed(1)} MB`;
|
|
107
|
+
return `${(n / 1024 ** 3).toFixed(2)} GB`;
|
|
108
|
+
}
|
package/src/vulkan.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// Backend opzionale: WebGPU/Dawn con selettore Vulkan effettivamente applicato.
|
|
2
|
+
// Binding e copia di ORT isolati nella cache; nessun provider standard sostituito.
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { isMainThread } from 'node:worker_threads';
|
|
10
|
+
|
|
11
|
+
export const ORT_VERSION = '1.30.0';
|
|
12
|
+
export const ORT_SOURCE = 'f2c39fe2f838cf35ce7da92824f5a5e3ee6e88a7';
|
|
13
|
+
export const VULKAN_REVISION = 1;
|
|
14
|
+
export const VULKAN_EP = 'pi-codeindex-vulkan-r1';
|
|
15
|
+
// Un solo Identity float32, anche per verificare il rifiuto di un backend invalido.
|
|
16
|
+
export const SMOKE_MODEL = Buffer.from('08083a370a100a015812015922084964656e746974791201675a0f0a0158120a0a08080112040a020801620f0a0159120a0a08080112040a0208014202100d', 'hex');
|
|
17
|
+
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
export function ortLibrary() {
|
|
20
|
+
const dir = path.dirname(require.resolve('onnxruntime-node/package.json'));
|
|
21
|
+
return path.join(dir, 'bin', 'napi-v6', process.platform, process.arch, 'libonnxruntime.so.1');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function vulkanCacheDir({ home = os.homedir(), platform = process.platform, arch = process.arch } = {}) {
|
|
25
|
+
return path.join(home, '.cache', 'pi-codeindex', 'native', `vulkan-ort-${ORT_VERSION}-r${VULKAN_REVISION}`, `${platform}-${arch}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function validateVulkanManifest(m, { platform = process.platform, arch = process.arch } = {}) {
|
|
29
|
+
if (!m || m.runtimeVersion !== ORT_VERSION || m.sourceRevision !== ORT_SOURCE ||
|
|
30
|
+
m.revision !== VULKAN_REVISION || m.platform !== platform || m.arch !== arch) {
|
|
31
|
+
throw new Error('manifest Vulkan incompatibile: riesegui npm run setup-vulkan');
|
|
32
|
+
}
|
|
33
|
+
for (const key of ['artifact', 'sourceLibraryHash', 'bindingHash', 'libraryHash']) {
|
|
34
|
+
if (typeof m[key] !== 'string' || !/^[a-f0-9]{64}$/.test(m[key])) throw new Error(`manifest Vulkan: ${key} invalido`);
|
|
35
|
+
}
|
|
36
|
+
if (m.library !== `libpi-codeindex-ort-vulkan-${m.sourceLibraryHash.slice(0, 16)}.so.1`) {
|
|
37
|
+
throw new Error('manifest Vulkan: nome libreria invalido');
|
|
38
|
+
}
|
|
39
|
+
return m;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Inventario leggero: non carica addon, driver o modelli. Gli hash vengono
|
|
43
|
+
// verificati prima del dlopen, non ad ogni richiesta di status/doctor.
|
|
44
|
+
export function vulkanStatus(options = {}) {
|
|
45
|
+
const platform = options.platform ?? process.platform;
|
|
46
|
+
const arch = options.arch ?? process.arch;
|
|
47
|
+
const dir = vulkanCacheDir(options);
|
|
48
|
+
if (platform !== 'linux' || arch !== 'x64') return { available: false, dir,
|
|
49
|
+
reason: 'setup Vulkan forzato disponibile inizialmente solo su Linux x64; questa piattaforma non è verificata' };
|
|
50
|
+
try {
|
|
51
|
+
const manifest = validateVulkanManifest(JSON.parse(readFileSync(path.join(dir, 'manifest.json'), 'utf8')), { platform, arch });
|
|
52
|
+
const artifactDir = path.join(dir, manifest.artifact);
|
|
53
|
+
const binding = path.join(artifactDir, 'onnxruntime_binding.node');
|
|
54
|
+
const library = path.join(artifactDir, manifest.library);
|
|
55
|
+
if (!existsSync(binding) || !existsSync(library)) throw new Error('artefatti Vulkan mancanti');
|
|
56
|
+
return { available: true, dir, binding, library, manifest, reason: 'binding isolato presente; integrità e inferenza da verificare' };
|
|
57
|
+
} catch (e) {
|
|
58
|
+
return { available: false, dir, reason: e.code === 'ENOENT' ? 'binding opzionale assente: esegui npm run setup-vulkan' : e.message };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function fileHash(file) {
|
|
63
|
+
return createHash('sha256').update(await fs.readFile(file)).digest('hex');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function verifyVulkanArtifacts(status, sourceLibrary = ortLibrary()) {
|
|
67
|
+
if (!status.available) throw new Error(status.reason);
|
|
68
|
+
const version = require('onnxruntime-node/package.json').version;
|
|
69
|
+
if (version !== ORT_VERSION) throw new Error(`binding Vulkan richiede onnxruntime-node ${ORT_VERSION}, trovato ${version}`);
|
|
70
|
+
for (const [file, expected] of [[sourceLibrary, status.manifest.sourceLibraryHash],
|
|
71
|
+
[status.binding, status.manifest.bindingHash], [status.library, status.manifest.libraryHash]]) {
|
|
72
|
+
if (await fileHash(file) !== expected) throw new Error(`integrità/runtime Vulkan cambiato (${path.basename(file)}): riesegui npm run setup-vulkan`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Prova negativa obbligatoria: impedisce di dichiarare Vulkan quando il binding
|
|
77
|
+
// ignora il selettore. Deve fallire PRIMA di inizializzare un device Dawn.
|
|
78
|
+
export function assertVulkanSelector(binding) {
|
|
79
|
+
const session = new binding.InferenceSession();
|
|
80
|
+
let rejected = false;
|
|
81
|
+
try {
|
|
82
|
+
session.loadModel(SMOKE_MODEL.buffer, SMOKE_MODEL.byteOffset, SMOKE_MODEL.byteLength,
|
|
83
|
+
{ executionProviders: [{ name: 'webgpu', dawnBackendType: '__codeindex_invalid_backend__' }] });
|
|
84
|
+
} catch (e) {
|
|
85
|
+
if (!/Invalid Dawn backend type/.test(e.message)) throw new Error(`selettore Vulkan non verificato: ${e.message}`);
|
|
86
|
+
rejected = true;
|
|
87
|
+
} finally {
|
|
88
|
+
try { session.dispose(); } catch { /* loadModel può non avere creato la sessione */ }
|
|
89
|
+
}
|
|
90
|
+
if (!rejected) throw new Error('il binding ignora dawnBackendType: Vulkan non può essere garantito');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const TYPES = [null, 'float32', 'uint8', 'int8', 'uint16', 'int16', 'int32', 'int64', 'string', 'bool',
|
|
94
|
+
'float16', 'float64', 'uint32', 'uint64', null, null, null, null, null, null, null, 'uint4', 'int4'];
|
|
95
|
+
const defer = (fn) => new Promise((resolve, reject) => setImmediate(() => { try { resolve(fn()); } catch (e) { reject(e); } }));
|
|
96
|
+
function metadata(entries) {
|
|
97
|
+
return entries.map((m) => {
|
|
98
|
+
if (!m.isTensor) return { name: m.name, isTensor: false };
|
|
99
|
+
const type = TYPES[m.type];
|
|
100
|
+
if (!type) throw new Error(`tipo ONNX non supportato: ${m.type}`);
|
|
101
|
+
return { name: m.name, isTensor: true, type, shape: m.shape.map((dim, i) => {
|
|
102
|
+
if (dim < -1) throw new Error(`dimensione ONNX invalida: ${dim}`);
|
|
103
|
+
return dim === -1 ? m.symbolicDimensions[i] : dim;
|
|
104
|
+
}) };
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Adattatore dell'interfaccia pubblica Backend di onnxruntime-common. Registrato
|
|
109
|
+
// con nome unico e priorità negativa: non entra mai nella selezione automatica
|
|
110
|
+
// di altri utenti di ONNX Runtime nello stesso processo.
|
|
111
|
+
export function createVulkanBackend(binding) {
|
|
112
|
+
return {
|
|
113
|
+
init: async () => {},
|
|
114
|
+
createInferenceSessionHandler: (model, options) => defer(() => {
|
|
115
|
+
const native = new binding.InferenceSession();
|
|
116
|
+
try {
|
|
117
|
+
const opts = { ...options, executionProviders: [{ name: 'webgpu', dawnBackendType: 'Vulkan' }] };
|
|
118
|
+
if (typeof model === 'string') native.loadModel(model, opts);
|
|
119
|
+
else native.loadModel(model.buffer, model.byteOffset, model.byteLength, opts);
|
|
120
|
+
const inputMetadata = metadata(native.inputMetadata);
|
|
121
|
+
const outputMetadata = metadata(native.outputMetadata);
|
|
122
|
+
return { inputNames: inputMetadata.map((m) => m.name), outputNames: outputMetadata.map((m) => m.name),
|
|
123
|
+
inputMetadata, outputMetadata,
|
|
124
|
+
run: (feeds, fetches, runOptions) => defer(() => native.run(feeds, fetches, runOptions)),
|
|
125
|
+
dispose: async () => native.dispose(), startProfiling: () => {}, endProfiling: () => native.endProfiling() };
|
|
126
|
+
} catch (e) {
|
|
127
|
+
try { native.dispose(); } catch { /* conserva l'errore originale */ }
|
|
128
|
+
throw e;
|
|
129
|
+
}
|
|
130
|
+
}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let registration;
|
|
135
|
+
export async function registerVulkanBackend() {
|
|
136
|
+
if (!registration) registration = (async () => {
|
|
137
|
+
const status = vulkanStatus();
|
|
138
|
+
await verifyVulkanArtifacts(status);
|
|
139
|
+
// Stessa policy dell'adapter Transformers.js, prima di caricare la copia
|
|
140
|
+
// privata di ORT. Non modifica env/backends di Transformers.js.
|
|
141
|
+
process.env.ORT_DISABLE_TELEMETRY = '1';
|
|
142
|
+
const ort = require('onnxruntime-node');
|
|
143
|
+
const binding = require(status.binding);
|
|
144
|
+
const levels = { verbose: 0, info: 1, warning: 2, error: 3, fatal: 4 };
|
|
145
|
+
binding.initOrtOnce(levels[ort.env.logLevel] ?? 2, ort.Tensor, isMainThread);
|
|
146
|
+
assertVulkanSelector(binding);
|
|
147
|
+
ort.registerBackend(VULKAN_EP, createVulkanBackend(binding), -10);
|
|
148
|
+
return VULKAN_EP;
|
|
149
|
+
})();
|
|
150
|
+
try { return await registration; } catch (e) { registration = undefined; throw e; }
|
|
151
|
+
}
|