@gmickel/gno 1.46.0 → 2.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/README.md +1 -1
- package/THIRD_PARTY_NOTICES.md +46 -0
- package/assets/skill/SKILL.md +7 -6
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip.sha256 +1 -0
- package/browser-extension/dist/chunk-4tc9v0ja.js +74 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/browser-extension/dist/preview.html +1 -1
- package/browser-extension/dist/service-worker.js +32 -33
- package/bunfig.toml +2 -0
- package/package.json +40 -26
- package/spec/cli.md +21 -4
- package/spec/db/schema.sql +146 -1
- package/spec/mcp.md +26 -0
- package/src/app/context-runtime-types.ts +3 -0
- package/src/app/context-runtime.ts +2 -0
- package/src/cli/commands/ask.ts +6 -1
- package/src/cli/commands/daemon.ts +21 -8
- package/src/cli/commands/embed.ts +77 -41
- package/src/cli/detach.ts +3 -2
- package/src/config/types.ts +3 -3
- package/src/converters/adapters/markitdownTs/adapter.ts +1 -2
- package/src/converters/adapters/officeparser/adapter.ts +1 -2
- package/src/converters/versions.ts +6 -8
- package/src/core/context-evidence.ts +8 -4
- package/src/core/job-manager.ts +95 -13
- package/src/core/network-boundary-inventory.ts +10 -0
- package/src/core/shutdown-budget.ts +45 -0
- package/src/embed/backlog.ts +107 -4
- package/src/embed/batch.ts +42 -2
- package/src/embed/fingerprint.ts +16 -0
- package/src/embed/retry.ts +113 -5
- package/src/embed/variant-backlog.ts +105 -0
- package/src/embed/variant-plan.ts +62 -0
- package/src/embed/variant-retry.ts +113 -0
- package/src/ingestion/graph-reconciliation.ts +327 -0
- package/src/ingestion/sync.ts +9 -272
- package/src/llm/http-inference.ts +6 -0
- package/src/llm/httpEmbedding.ts +37 -6
- package/src/llm/httpGeneration.ts +18 -3
- package/src/llm/httpRerank.ts +23 -5
- package/src/llm/inference-cancellation.ts +168 -0
- package/src/llm/inference-scope.ts +202 -0
- package/src/llm/lazy-ports.ts +115 -0
- package/src/llm/native-worker/client.ts +541 -0
- package/src/llm/native-worker/dispatcher.ts +228 -0
- package/src/llm/native-worker/embedding-identity.ts +33 -0
- package/src/llm/native-worker/entry.ts +173 -0
- package/src/llm/native-worker/errors.ts +32 -0
- package/src/llm/native-worker/evaluation.ts +16 -0
- package/src/llm/native-worker/owned-exit.ts +108 -0
- package/src/llm/native-worker/owner.ts +141 -0
- package/src/llm/native-worker/ports.ts +317 -0
- package/src/llm/native-worker/protocol.ts +442 -0
- package/src/llm/native-worker/runtime-config.ts +92 -0
- package/src/llm/nodeLlamaCpp/adapter.ts +77 -20
- package/src/llm/nodeLlamaCpp/embedding.ts +130 -46
- package/src/llm/nodeLlamaCpp/generation.ts +34 -5
- package/src/llm/nodeLlamaCpp/lifecycle-options.ts +99 -0
- package/src/llm/nodeLlamaCpp/lifecycle.ts +209 -204
- package/src/llm/nodeLlamaCpp/rerank-capacity.ts +111 -0
- package/src/llm/nodeLlamaCpp/rerank.ts +118 -27
- package/src/llm/nodeLlamaCpp/simulator-handle.ts +73 -0
- package/src/llm/nodeLlamaCpp/simulator-install.ts +124 -0
- package/src/llm/nodeLlamaCpp/simulator-session.ts +240 -0
- package/src/llm/nodeLlamaCpp/simulator-types.ts +80 -0
- package/src/llm/types.ts +35 -5
- package/src/mcp/context.ts +27 -0
- package/src/mcp/http-transport.ts +12 -10
- package/src/mcp/server.ts +3 -0
- package/src/mcp/tool-profile.ts +30 -8
- package/src/mcp/tools/context.ts +8 -11
- package/src/mcp/tools/embed.ts +1 -1
- package/src/mcp/tools/index-cmd.ts +1 -1
- package/src/mcp/tools/index.ts +10 -8
- package/src/mcp/tools/query.ts +14 -30
- package/src/mcp/tools/vsearch.ts +1 -1
- package/src/pipeline/answer.ts +23 -3
- package/src/pipeline/claim-verifier.ts +6 -0
- package/src/pipeline/expansion.ts +43 -40
- package/src/pipeline/explain.ts +6 -2
- package/src/pipeline/filters.ts +63 -0
- package/src/pipeline/fusion.ts +29 -9
- package/src/pipeline/graph-retrieval.ts +29 -9
- package/src/pipeline/hybrid.ts +198 -55
- package/src/pipeline/hydration.ts +161 -0
- package/src/pipeline/owner-fusion.ts +87 -0
- package/src/pipeline/rerank.ts +35 -11
- package/src/pipeline/search.ts +13 -2
- package/src/pipeline/types.ts +5 -3
- package/src/pipeline/vsearch.ts +87 -7
- package/src/sdk/client.ts +47 -3
- package/src/sdk/embed.ts +63 -39
- package/src/serve/background-runtime.ts +1 -1
- package/src/serve/context.ts +41 -56
- package/src/serve/embed-scheduler.ts +58 -35
- package/src/serve/public/components/IndexingProgress.tsx +46 -60
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/lib/shiki-language-ids.ts +14 -0
- package/src/serve/resident-admission.ts +36 -36
- package/src/serve/resident-background-work.ts +20 -2
- package/src/serve/resident-request.ts +11 -5
- package/src/serve/resident-runtime.ts +97 -61
- package/src/serve/resident-shutdown.ts +153 -0
- package/src/serve/routes/api.ts +3 -1
- package/src/serve/server.ts +47 -26
- package/src/store/migrations/028-vector-variants.ts +54 -0
- package/src/store/migrations/029-graph-reference-state.ts +77 -0
- package/src/store/migrations/index.ts +4 -0
- package/src/store/sqlite/adapter.ts +251 -183
- package/src/store/sqlite/eligibility.ts +174 -0
- package/src/store/sqlite/graph-edge-application.ts +66 -0
- package/src/store/sqlite/graph-reference-state.ts +194 -0
- package/src/store/sqlite/legacy-vector-ownership.ts +79 -0
- package/src/store/types.ts +80 -12
- package/src/store/vector/eligibility.ts +36 -0
- package/src/store/vector/freshness.ts +33 -6
- package/src/store/vector/lazy.ts +81 -0
- package/src/store/vector/sqlite-vec.ts +106 -54
- package/src/store/vector/stats.ts +14 -3
- package/src/store/vector/types.ts +35 -2
- package/src/store/vector/variant-search.ts +192 -0
- package/src/store/vector/variants.ts +451 -0
- package/vendor/converters/markitdown-ts/LICENSE +21 -0
- package/vendor/converters/markitdown-ts/dist/index.cjs +1180 -0
- package/vendor/converters/markitdown-ts/dist/index.d.cts +46 -0
- package/vendor/converters/markitdown-ts/dist/index.d.mts +46 -0
- package/vendor/converters/markitdown-ts/dist/index.d.ts +46 -0
- package/vendor/converters/markitdown-ts/dist/index.mjs +1152 -0
- package/vendor/converters/markitdown-ts/package.json +77 -0
- package/vendor/converters/officeparser/LICENSE +21 -0
- package/vendor/converters/officeparser/dist/OfficeConverter.d.ts +47 -0
- package/vendor/converters/officeparser/dist/OfficeConverter.js +76 -0
- package/vendor/converters/officeparser/dist/OfficeGenerator.d.ts +23 -0
- package/vendor/converters/officeparser/dist/OfficeGenerator.js +73 -0
- package/vendor/converters/officeparser/dist/OfficeParser.d.ts +106 -0
- package/vendor/converters/officeparser/dist/OfficeParser.js +332 -0
- package/vendor/converters/officeparser/dist/cli.d.ts +28 -0
- package/vendor/converters/officeparser/dist/cli.js +381 -0
- package/vendor/converters/officeparser/dist/defaults.d.ts +41 -0
- package/vendor/converters/officeparser/dist/defaults.js +218 -0
- package/vendor/converters/officeparser/dist/generators/BaseGenerator.d.ts +107 -0
- package/vendor/converters/officeparser/dist/generators/BaseGenerator.js +248 -0
- package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.d.ts +82 -0
- package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.js +797 -0
- package/vendor/converters/officeparser/dist/generators/CsvGenerator.d.ts +38 -0
- package/vendor/converters/officeparser/dist/generators/CsvGenerator.js +245 -0
- package/vendor/converters/officeparser/dist/generators/EpubGenerator.d.ts +43 -0
- package/vendor/converters/officeparser/dist/generators/EpubGenerator.js +315 -0
- package/vendor/converters/officeparser/dist/generators/HtmlGenerator.d.ts +59 -0
- package/vendor/converters/officeparser/dist/generators/HtmlGenerator.js +1942 -0
- package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.d.ts +96 -0
- package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.js +1175 -0
- package/vendor/converters/officeparser/dist/generators/PdfGenerator.d.ts +22 -0
- package/vendor/converters/officeparser/dist/generators/PdfGenerator.js +194 -0
- package/vendor/converters/officeparser/dist/generators/RtfGenerator.d.ts +29 -0
- package/vendor/converters/officeparser/dist/generators/RtfGenerator.js +316 -0
- package/vendor/converters/officeparser/dist/generators/TextGenerator.d.ts +13 -0
- package/vendor/converters/officeparser/dist/generators/TextGenerator.js +201 -0
- package/vendor/converters/officeparser/dist/index.d.ts +60 -0
- package/vendor/converters/officeparser/dist/index.js +72 -0
- package/vendor/converters/officeparser/dist/index.mjs +18 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.d.ts +2621 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.iife.js +1336 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.mjs +1335 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.slim.d.ts +2621 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.slim.iife.js +1336 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.slim.mjs +1335 -0
- package/vendor/converters/officeparser/dist/parsers/CsvParser.d.ts +9 -0
- package/vendor/converters/officeparser/dist/parsers/CsvParser.js +115 -0
- package/vendor/converters/officeparser/dist/parsers/EpubParser.d.ts +8 -0
- package/vendor/converters/officeparser/dist/parsers/EpubParser.js +217 -0
- package/vendor/converters/officeparser/dist/parsers/ExcelParser.d.ts +32 -0
- package/vendor/converters/officeparser/dist/parsers/ExcelParser.js +736 -0
- package/vendor/converters/officeparser/dist/parsers/HtmlParser.d.ts +2 -0
- package/vendor/converters/officeparser/dist/parsers/HtmlParser.js +1287 -0
- package/vendor/converters/officeparser/dist/parsers/MarkdownParser.d.ts +2 -0
- package/vendor/converters/officeparser/dist/parsers/MarkdownParser.js +1272 -0
- package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.d.ts +31 -0
- package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.js +1819 -0
- package/vendor/converters/officeparser/dist/parsers/PdfParser.d.ts +67 -0
- package/vendor/converters/officeparser/dist/parsers/PdfParser.js +848 -0
- package/vendor/converters/officeparser/dist/parsers/PowerPointParser.d.ts +32 -0
- package/vendor/converters/officeparser/dist/parsers/PowerPointParser.js +950 -0
- package/vendor/converters/officeparser/dist/parsers/RtfParser.d.ts +187 -0
- package/vendor/converters/officeparser/dist/parsers/RtfParser.js +1801 -0
- package/vendor/converters/officeparser/dist/parsers/WordParser.d.ts +79 -0
- package/vendor/converters/officeparser/dist/parsers/WordParser.js +1177 -0
- package/vendor/converters/officeparser/dist/sbom.cdx.json +1763 -0
- package/vendor/converters/officeparser/dist/types.d.ts +2507 -0
- package/vendor/converters/officeparser/dist/types.js +107 -0
- package/vendor/converters/officeparser/dist/utils/astUtils.d.ts +16 -0
- package/vendor/converters/officeparser/dist/utils/astUtils.js +33 -0
- package/vendor/converters/officeparser/dist/utils/chartUtils.d.ts +6 -0
- package/vendor/converters/officeparser/dist/utils/chartUtils.js +257 -0
- package/vendor/converters/officeparser/dist/utils/configUtils.d.ts +44 -0
- package/vendor/converters/officeparser/dist/utils/configUtils.js +315 -0
- package/vendor/converters/officeparser/dist/utils/dateUtils.d.ts +17 -0
- package/vendor/converters/officeparser/dist/utils/dateUtils.js +69 -0
- package/vendor/converters/officeparser/dist/utils/envUtils.d.ts +29 -0
- package/vendor/converters/officeparser/dist/utils/envUtils.js +152 -0
- package/vendor/converters/officeparser/dist/utils/errorUtils.d.ts +72 -0
- package/vendor/converters/officeparser/dist/utils/errorUtils.js +245 -0
- package/vendor/converters/officeparser/dist/utils/imageUtils.d.ts +66 -0
- package/vendor/converters/officeparser/dist/utils/imageUtils.js +133 -0
- package/vendor/converters/officeparser/dist/utils/mathUtils.d.ts +42 -0
- package/vendor/converters/officeparser/dist/utils/mathUtils.js +385 -0
- package/vendor/converters/officeparser/dist/utils/moduleLoader.d.ts +18 -0
- package/vendor/converters/officeparser/dist/utils/moduleLoader.js +106 -0
- package/vendor/converters/officeparser/dist/utils/ocrUtils.d.ts +42 -0
- package/vendor/converters/officeparser/dist/utils/ocrUtils.js +428 -0
- package/vendor/converters/officeparser/dist/utils/sanitize.d.ts +148 -0
- package/vendor/converters/officeparser/dist/utils/sanitize.js +344 -0
- package/vendor/converters/officeparser/dist/utils/sheetUtils.d.ts +7 -0
- package/vendor/converters/officeparser/dist/utils/sheetUtils.js +35 -0
- package/vendor/converters/officeparser/dist/utils/styleMapper.d.ts +36 -0
- package/vendor/converters/officeparser/dist/utils/styleMapper.js +224 -0
- package/vendor/converters/officeparser/dist/utils/xmlUtils.d.ts +163 -0
- package/vendor/converters/officeparser/dist/utils/xmlUtils.js +461 -0
- package/vendor/converters/officeparser/dist/utils/zipUtils.d.ts +134 -0
- package/vendor/converters/officeparser/dist/utils/zipUtils.js +337 -0
- package/vendor/converters/officeparser/package.json +147 -0
- package/vendor/converters/upstream-manifest.json +124 -0
- package/vendor/dependency-fixes/README.md +77 -0
- package/vendor/dependency-fixes/vendor-converters.py +83 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.46.0.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.46.0.zip.sha256 +0 -1
- package/browser-extension/dist/chunk-627emwpj.js +0 -75
- /package/browser-extension/dist/{chunk-ydfx5d7p.css → chunk-z74y8n8c.css} +0 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// Bun has no realpath API; canonical identity must be rechecked in the child.
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
import type { LlmResult } from "../types";
|
|
5
|
+
import type { NativeEvaluationOptions } from "./evaluation";
|
|
6
|
+
import type { ApprovedModel, NativeRequest, NativeResponse } from "./protocol";
|
|
7
|
+
import type { NativeRuntimeConfig } from "./runtime-config";
|
|
8
|
+
|
|
9
|
+
import { inferenceFailedError } from "../errors";
|
|
10
|
+
import { NodeLlamaCppEmbedding } from "../nodeLlamaCpp/embedding";
|
|
11
|
+
import { NodeLlamaCppGeneration } from "../nodeLlamaCpp/generation";
|
|
12
|
+
import { ModelManager } from "../nodeLlamaCpp/lifecycle";
|
|
13
|
+
import { NodeLlamaCppRerank } from "../nodeLlamaCpp/rerank";
|
|
14
|
+
import {
|
|
15
|
+
fileIdentity,
|
|
16
|
+
fingerprintModel,
|
|
17
|
+
fingerprintRuntime,
|
|
18
|
+
} from "./embedding-identity";
|
|
19
|
+
import { NativeWorkerError } from "./errors";
|
|
20
|
+
import { checkEvaluation } from "./evaluation";
|
|
21
|
+
import { splitEmbeddingRequest } from "./protocol";
|
|
22
|
+
import { wireError } from "./runtime-config";
|
|
23
|
+
|
|
24
|
+
type Port = NodeLlamaCppEmbedding | NodeLlamaCppGeneration | NodeLlamaCppRerank;
|
|
25
|
+
|
|
26
|
+
/** Imported only by the native child. No discovery, downloads or parent state. */
|
|
27
|
+
export class NativeDispatcher {
|
|
28
|
+
private readonly manager: ModelManager;
|
|
29
|
+
private readonly ports = new Map<string, Port>();
|
|
30
|
+
private readonly files = new Map<
|
|
31
|
+
string,
|
|
32
|
+
{ path: string; identity: string; fingerprint?: string }
|
|
33
|
+
>();
|
|
34
|
+
|
|
35
|
+
constructor(private readonly config: NativeRuntimeConfig) {
|
|
36
|
+
this.manager = new ModelManager(
|
|
37
|
+
{
|
|
38
|
+
activePreset: "native-worker",
|
|
39
|
+
presets: [],
|
|
40
|
+
expandContextSize: 2048,
|
|
41
|
+
loadTimeout: config.loadTimeout,
|
|
42
|
+
inferenceTimeout: config.inferenceTimeout,
|
|
43
|
+
warmModelTtl: config.warmModelTtl,
|
|
44
|
+
},
|
|
45
|
+
true
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private async port(model: ApprovedModel): Promise<Port> {
|
|
50
|
+
if (
|
|
51
|
+
(await realpath(model.path)) !== model.path ||
|
|
52
|
+
!(await Bun.file(model.path).exists())
|
|
53
|
+
) {
|
|
54
|
+
throw new NativeWorkerError("protocol");
|
|
55
|
+
}
|
|
56
|
+
const existing = this.ports.get(model.id);
|
|
57
|
+
// ModelManager retains weights by URI after a port disposes its contexts.
|
|
58
|
+
// Keep provenance at that same lifetime, not at the shorter port lifetime.
|
|
59
|
+
const file = this.files.get(model.modelUri);
|
|
60
|
+
const identity = await fileIdentity(model.path);
|
|
61
|
+
if (file && (file.path !== model.path || file.identity !== identity))
|
|
62
|
+
throw new NativeWorkerError("stale_generation");
|
|
63
|
+
if (existing) return existing;
|
|
64
|
+
const fingerprint =
|
|
65
|
+
model.type === "embed"
|
|
66
|
+
? (file?.fingerprint ?? (await fingerprintModel(model.path)))
|
|
67
|
+
: file?.fingerprint;
|
|
68
|
+
if (identity !== (await fileIdentity(model.path)))
|
|
69
|
+
throw new NativeWorkerError("stale_generation");
|
|
70
|
+
const Constructor =
|
|
71
|
+
model.type === "embed"
|
|
72
|
+
? NodeLlamaCppEmbedding
|
|
73
|
+
: model.type === "rerank"
|
|
74
|
+
? NodeLlamaCppRerank
|
|
75
|
+
: NodeLlamaCppGeneration;
|
|
76
|
+
const port = new Constructor(this.manager, model.modelUri, model.path);
|
|
77
|
+
this.ports.set(model.id, port);
|
|
78
|
+
this.files.set(model.modelUri, { path: model.path, identity, fingerprint });
|
|
79
|
+
return port;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async execute(
|
|
83
|
+
request: NativeRequest,
|
|
84
|
+
options?: NativeEvaluationOptions
|
|
85
|
+
): Promise<{ response: NativeResponse; activity: boolean }> {
|
|
86
|
+
const model = this.config.models.find(
|
|
87
|
+
(entry) => entry.id === request.modelId
|
|
88
|
+
);
|
|
89
|
+
if (!model) throw new NativeWorkerError("protocol");
|
|
90
|
+
// Metadata protects in-flight initialization without renewing idle models.
|
|
91
|
+
const lease = this.manager.acquireLease(
|
|
92
|
+
model.modelUri,
|
|
93
|
+
!["init", "dispose"].includes(request.op)
|
|
94
|
+
);
|
|
95
|
+
const before = this.manager.getLifecycleStats().loadAttempts;
|
|
96
|
+
let response: Omit<NativeResponse, "lifecycle">;
|
|
97
|
+
try {
|
|
98
|
+
const result = await this.run(request, model, options).catch(
|
|
99
|
+
(cause: unknown) => ({
|
|
100
|
+
ok: false as const,
|
|
101
|
+
error: inferenceFailedError(model.modelUri, cause),
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
// Lazy loads and inference may outlive a file mutation. Never publish their
|
|
105
|
+
// completion under provenance that no longer matches the approved artifact.
|
|
106
|
+
if (
|
|
107
|
+
request.op !== "dispose" &&
|
|
108
|
+
((await realpath(model.path)) !== model.path ||
|
|
109
|
+
this.files.get(model.modelUri)?.identity !==
|
|
110
|
+
(await fileIdentity(model.path)))
|
|
111
|
+
)
|
|
112
|
+
throw new NativeWorkerError("stale_generation");
|
|
113
|
+
response = {
|
|
114
|
+
version: 1,
|
|
115
|
+
generation: request.generation,
|
|
116
|
+
requestId: request.requestId,
|
|
117
|
+
op: request.op,
|
|
118
|
+
result: result.ok
|
|
119
|
+
? result
|
|
120
|
+
: { ok: false, error: wireError(result.error) },
|
|
121
|
+
};
|
|
122
|
+
} finally {
|
|
123
|
+
lease.release();
|
|
124
|
+
}
|
|
125
|
+
// Publish settled ownership, not the request lease held during evaluation.
|
|
126
|
+
const lifecycle = this.manager.getLifecycleStats();
|
|
127
|
+
return {
|
|
128
|
+
response: { ...response, lifecycle },
|
|
129
|
+
activity:
|
|
130
|
+
lifecycle.loadAttempts !== before ||
|
|
131
|
+
!["init", "dispose"].includes(request.op),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private async run(
|
|
136
|
+
request: NativeRequest,
|
|
137
|
+
model: ApprovedModel,
|
|
138
|
+
options?: NativeEvaluationOptions
|
|
139
|
+
): Promise<
|
|
140
|
+
LlmResult<Extract<NativeResponse["result"], { ok: true }>["value"]>
|
|
141
|
+
> {
|
|
142
|
+
if (request.op === "dispose") {
|
|
143
|
+
await this.ports.get(model.id)?.dispose();
|
|
144
|
+
this.ports.delete(model.id);
|
|
145
|
+
return { ok: true, value: null };
|
|
146
|
+
}
|
|
147
|
+
checkEvaluation(options);
|
|
148
|
+
const port = await this.port(model);
|
|
149
|
+
checkEvaluation(options);
|
|
150
|
+
switch (request.op) {
|
|
151
|
+
case "init": {
|
|
152
|
+
if (port instanceof NodeLlamaCppEmbedding) {
|
|
153
|
+
const result = await port.init(options);
|
|
154
|
+
if (!result.ok) return result;
|
|
155
|
+
const file = this.files.get(model.modelUri);
|
|
156
|
+
if (
|
|
157
|
+
!file?.fingerprint ||
|
|
158
|
+
file.identity !== (await fileIdentity(model.path))
|
|
159
|
+
)
|
|
160
|
+
throw new NativeWorkerError("stale_generation");
|
|
161
|
+
const settings = port.getContextIdentity();
|
|
162
|
+
const llama = await this.manager.getLlama();
|
|
163
|
+
return {
|
|
164
|
+
ok: true,
|
|
165
|
+
value: {
|
|
166
|
+
dimensions: port.dimensions(),
|
|
167
|
+
structuredOutput: "none",
|
|
168
|
+
embeddingIdentity: {
|
|
169
|
+
contextSize: settings.contextSize,
|
|
170
|
+
truncationPolicy: settings.truncationPolicy,
|
|
171
|
+
modelFingerprint: file.fingerprint,
|
|
172
|
+
runtimeFingerprint: fingerprintRuntime({
|
|
173
|
+
...settings,
|
|
174
|
+
gpu: llama.gpu,
|
|
175
|
+
cpuMathCores: llama.cpuMathCores,
|
|
176
|
+
}),
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
ok: true,
|
|
183
|
+
value: {
|
|
184
|
+
structuredOutput:
|
|
185
|
+
port instanceof NodeLlamaCppGeneration ? "json_schema" : "none",
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
case "embed":
|
|
190
|
+
if (port instanceof NodeLlamaCppEmbedding)
|
|
191
|
+
return port.embed(request.text, options);
|
|
192
|
+
break;
|
|
193
|
+
case "embedBatch":
|
|
194
|
+
if (port instanceof NodeLlamaCppEmbedding) {
|
|
195
|
+
const vectors: number[][] = [];
|
|
196
|
+
for (const texts of splitEmbeddingRequest(request)) {
|
|
197
|
+
const result = await port.embedBatch(texts, options);
|
|
198
|
+
if (!result.ok) return result;
|
|
199
|
+
for (const vector of result.value) vectors.push(vector);
|
|
200
|
+
}
|
|
201
|
+
return { ok: true, value: vectors };
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
case "generate":
|
|
205
|
+
if (port instanceof NodeLlamaCppGeneration)
|
|
206
|
+
return port.generate(request.prompt, request.params, options);
|
|
207
|
+
break;
|
|
208
|
+
case "rerank":
|
|
209
|
+
if (port instanceof NodeLlamaCppRerank)
|
|
210
|
+
return port.rerank(request.query, request.documents, options);
|
|
211
|
+
break;
|
|
212
|
+
default:
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
throw new NativeWorkerError("protocol");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async dispose(): Promise<void> {
|
|
219
|
+
const results = await Promise.allSettled(
|
|
220
|
+
Array.from(this.ports.values(), (port) => port.dispose())
|
|
221
|
+
);
|
|
222
|
+
this.ports.clear();
|
|
223
|
+
this.files.clear();
|
|
224
|
+
await this.manager.disposeAll();
|
|
225
|
+
if (results.some((result) => result.status === "rejected"))
|
|
226
|
+
throw new NativeWorkerError("exited");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Bun has no inode/ctime identity API. Check replacement during hashing/loading.
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
const nativePackage: { version: string } = await Bun.file(
|
|
5
|
+
new URL("../package.json", import.meta.resolve("node-llama-cpp"))
|
|
6
|
+
).json();
|
|
7
|
+
|
|
8
|
+
export async function fileIdentity(path: string): Promise<string> {
|
|
9
|
+
const info = await stat(path, { bigint: true });
|
|
10
|
+
return [info.dev, info.ino, info.size, info.mtimeNs, info.ctimeNs].join(":");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Stream the actual child-approved artifact; never buffer a GGUF in parent RAM. */
|
|
14
|
+
export async function fingerprintModel(path: string): Promise<string> {
|
|
15
|
+
const hash = new Bun.CryptoHasher("sha256");
|
|
16
|
+
for await (const bytes of Bun.file(path).stream()) hash.update(bytes);
|
|
17
|
+
return hash.digest("hex");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function fingerprintRuntime(settings: Record<string, unknown>): string {
|
|
21
|
+
return new Bun.CryptoHasher("sha256")
|
|
22
|
+
.update(
|
|
23
|
+
JSON.stringify({
|
|
24
|
+
policy: "native-embedding-v1",
|
|
25
|
+
bun: Bun.version,
|
|
26
|
+
nativeVersion: nativePackage.version,
|
|
27
|
+
platform: process.platform,
|
|
28
|
+
arch: process.arch,
|
|
29
|
+
...settings,
|
|
30
|
+
})
|
|
31
|
+
)
|
|
32
|
+
.digest("hex");
|
|
33
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { NativeDispatcher } from "./dispatcher";
|
|
2
|
+
import { NativeWorkerError } from "./errors";
|
|
3
|
+
import {
|
|
4
|
+
frameNativeMessage,
|
|
5
|
+
NativeFrameDecoder,
|
|
6
|
+
parseNativeRequest,
|
|
7
|
+
NativeCancellationSchema,
|
|
8
|
+
} from "./protocol";
|
|
9
|
+
import {
|
|
10
|
+
NativeAckSchema,
|
|
11
|
+
NativeRegistrationSchema,
|
|
12
|
+
NativeRuntimeConfigSchema,
|
|
13
|
+
} from "./runtime-config";
|
|
14
|
+
|
|
15
|
+
const config = NativeRuntimeConfigSchema.parse(
|
|
16
|
+
JSON.parse(process.argv[2] ?? "null")
|
|
17
|
+
);
|
|
18
|
+
if (!process.send) throw new Error("Native worker requires parent IPC");
|
|
19
|
+
const dispatcher = new NativeDispatcher(config);
|
|
20
|
+
const decoder = new NativeFrameDecoder(config.generation);
|
|
21
|
+
let active: Promise<void> | undefined;
|
|
22
|
+
let activeRequest: { id: number; controller: AbortController } | undefined;
|
|
23
|
+
let awaitingAck: number | undefined;
|
|
24
|
+
let closing: Promise<void> | undefined;
|
|
25
|
+
let lastActivity = Date.now();
|
|
26
|
+
let activity = false;
|
|
27
|
+
let idleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
28
|
+
|
|
29
|
+
function armIdle(): void {
|
|
30
|
+
clearTimeout(idleTimer);
|
|
31
|
+
if (active || awaitingAck || closing) return;
|
|
32
|
+
idleTimer = setTimeout(
|
|
33
|
+
() => process.send?.("idle"),
|
|
34
|
+
Math.max(0, lastActivity + config.warmModelTtl - Date.now())
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function shutdown(): Promise<void> {
|
|
39
|
+
if (closing) return closing;
|
|
40
|
+
clearTimeout(idleTimer);
|
|
41
|
+
// Finite termination also covers stuck native work and disconnected parents.
|
|
42
|
+
const force = setTimeout(() => process.exit(1), 900);
|
|
43
|
+
closing = (async () => {
|
|
44
|
+
try {
|
|
45
|
+
await active;
|
|
46
|
+
await dispatcher.dispose();
|
|
47
|
+
clearTimeout(force);
|
|
48
|
+
process.exit(0);
|
|
49
|
+
} catch {
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
return closing;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
process.on("disconnect", () => {
|
|
57
|
+
void shutdown();
|
|
58
|
+
});
|
|
59
|
+
process.on("SIGTERM", () => {
|
|
60
|
+
void shutdown();
|
|
61
|
+
});
|
|
62
|
+
process.on("message", (message: unknown) => {
|
|
63
|
+
if (closing) return;
|
|
64
|
+
if (typeof message === "object" && message !== null && "cancel" in message) {
|
|
65
|
+
const parsed = NativeCancellationSchema.safeParse(message);
|
|
66
|
+
if (
|
|
67
|
+
!parsed.success ||
|
|
68
|
+
parsed.data.generation !== config.generation ||
|
|
69
|
+
(parsed.data.requestId !== activeRequest?.id &&
|
|
70
|
+
parsed.data.requestId !== awaitingAck)
|
|
71
|
+
) {
|
|
72
|
+
void shutdown();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (activeRequest?.id === parsed.data.requestId)
|
|
76
|
+
activeRequest.controller.abort(
|
|
77
|
+
new DOMException(
|
|
78
|
+
"Inference cancelled",
|
|
79
|
+
parsed.data.cancel === "timeout" ? "TimeoutError" : "AbortError"
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (message === "shutdown") {
|
|
85
|
+
void shutdown();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (
|
|
89
|
+
typeof message === "object" &&
|
|
90
|
+
message !== null &&
|
|
91
|
+
"register" in message
|
|
92
|
+
) {
|
|
93
|
+
const parsed = NativeRegistrationSchema.safeParse(message);
|
|
94
|
+
if (
|
|
95
|
+
!parsed.success ||
|
|
96
|
+
config.models.some((model) => model.id === parsed.data.register.id)
|
|
97
|
+
) {
|
|
98
|
+
void shutdown();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const updated = NativeRuntimeConfigSchema.safeParse({
|
|
102
|
+
...config,
|
|
103
|
+
models: [...config.models, parsed.data.register],
|
|
104
|
+
});
|
|
105
|
+
if (!updated.success) {
|
|
106
|
+
void shutdown();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
config.models.push(parsed.data.register);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (typeof message === "object" && message !== null && "ack" in message) {
|
|
113
|
+
if (
|
|
114
|
+
!NativeAckSchema.safeParse(message).success ||
|
|
115
|
+
message.ack !== awaitingAck ||
|
|
116
|
+
awaitingAck === undefined
|
|
117
|
+
) {
|
|
118
|
+
void shutdown();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
awaitingAck = undefined;
|
|
122
|
+
if (activity) lastActivity = Date.now();
|
|
123
|
+
activity = false;
|
|
124
|
+
armIdle();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
if (!(message instanceof Uint8Array) || active || awaitingAck)
|
|
129
|
+
throw new NativeWorkerError("protocol");
|
|
130
|
+
clearTimeout(idleTimer);
|
|
131
|
+
const decoded = decoder.push(message);
|
|
132
|
+
if (decoded === undefined) return;
|
|
133
|
+
const request = parseNativeRequest(
|
|
134
|
+
decoded,
|
|
135
|
+
config.generation,
|
|
136
|
+
config.models
|
|
137
|
+
);
|
|
138
|
+
const controller = new AbortController();
|
|
139
|
+
activeRequest = { id: request.requestId, controller };
|
|
140
|
+
let executionStarted = false;
|
|
141
|
+
active = (async () => {
|
|
142
|
+
const result = await dispatcher.execute(request, {
|
|
143
|
+
signal: controller.signal,
|
|
144
|
+
deadlineAt: request.deadlineAt,
|
|
145
|
+
onExecutionStart: () => {
|
|
146
|
+
if (executionStarted) return;
|
|
147
|
+
executionStarted = true;
|
|
148
|
+
process.send?.({
|
|
149
|
+
version: 1,
|
|
150
|
+
generation: config.generation,
|
|
151
|
+
requestId: request.requestId,
|
|
152
|
+
executionStarted: true,
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
activity = result.activity;
|
|
157
|
+
awaitingAck = request.requestId;
|
|
158
|
+
for (const frame of frameNativeMessage(result.response))
|
|
159
|
+
process.send?.(frame);
|
|
160
|
+
})()
|
|
161
|
+
.catch(() => {
|
|
162
|
+
void shutdown();
|
|
163
|
+
})
|
|
164
|
+
.finally(() => {
|
|
165
|
+
active = undefined;
|
|
166
|
+
activeRequest = undefined;
|
|
167
|
+
});
|
|
168
|
+
} catch {
|
|
169
|
+
void shutdown();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
process.send("ready");
|
|
173
|
+
armIdle();
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type LlmError, llmError } from "../errors";
|
|
2
|
+
|
|
3
|
+
export type NativeWorkerFailure =
|
|
4
|
+
| "protocol"
|
|
5
|
+
| "overloaded"
|
|
6
|
+
| "oversized"
|
|
7
|
+
| "stale_generation"
|
|
8
|
+
| "duplicate_completion"
|
|
9
|
+
| "exited"
|
|
10
|
+
| "timeout";
|
|
11
|
+
|
|
12
|
+
/** Stable host errors; never include payloads or child stderr in caller output. */
|
|
13
|
+
export class NativeWorkerError extends Error {
|
|
14
|
+
readonly detail: LlmError;
|
|
15
|
+
|
|
16
|
+
constructor(readonly reason: NativeWorkerFailure) {
|
|
17
|
+
super(`Native worker failure: ${reason}`);
|
|
18
|
+
this.name = "NativeWorkerError";
|
|
19
|
+
this.detail = llmError(
|
|
20
|
+
reason === "timeout" ? "TIMEOUT" : "INFERENCE_FAILED",
|
|
21
|
+
{
|
|
22
|
+
message: this.message,
|
|
23
|
+
retryable: [
|
|
24
|
+
"overloaded",
|
|
25
|
+
"stale_generation",
|
|
26
|
+
"exited",
|
|
27
|
+
"timeout",
|
|
28
|
+
].includes(reason),
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { InferenceOptions } from "../types";
|
|
2
|
+
|
|
3
|
+
export interface NativeEvaluationOptions extends InferenceOptions {
|
|
4
|
+
/** Child control receipt immediately before the first actual evaluation. */
|
|
5
|
+
onExecutionStart?: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function checkEvaluation(options?: NativeEvaluationOptions): void {
|
|
9
|
+
options?.signal?.throwIfAborted();
|
|
10
|
+
if (options?.deadlineAt !== undefined && Date.now() >= options.deadlineAt)
|
|
11
|
+
throw new DOMException("Inference deadline exceeded", "TimeoutError");
|
|
12
|
+
}
|
|
13
|
+
export function startEvaluation(options?: NativeEvaluationOptions): void {
|
|
14
|
+
checkEvaluation(options);
|
|
15
|
+
options?.onExecutionStart?.();
|
|
16
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Owner } from "./owner";
|
|
2
|
+
|
|
3
|
+
import { settlesBy, SHUTDOWN_EXIT_MS } from "../../core/shutdown-budget";
|
|
4
|
+
|
|
5
|
+
export interface NativeDisposeOptions {
|
|
6
|
+
/** Absolute monotonic deadline; internal shutdown control, never caller config. */
|
|
7
|
+
deadline?: number;
|
|
8
|
+
force?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface OwnedExitControl {
|
|
12
|
+
deadline: number;
|
|
13
|
+
force: boolean;
|
|
14
|
+
changed: AbortController;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type OwnedChild = Pick<
|
|
18
|
+
ReturnType<typeof Bun.spawn>,
|
|
19
|
+
"pid" | "kill" | "send" | "exited"
|
|
20
|
+
>;
|
|
21
|
+
|
|
22
|
+
async function waitForExit(
|
|
23
|
+
child: OwnedChild,
|
|
24
|
+
control: OwnedExitControl,
|
|
25
|
+
phaseEnd: number,
|
|
26
|
+
graceful: boolean
|
|
27
|
+
): Promise<boolean> {
|
|
28
|
+
while (true) {
|
|
29
|
+
const deadline = Math.min(
|
|
30
|
+
phaseEnd,
|
|
31
|
+
control.deadline - (graceful ? SHUTDOWN_EXIT_MS : 0)
|
|
32
|
+
);
|
|
33
|
+
if (graceful && control.force) return false;
|
|
34
|
+
if (await settlesBy(child.exited, deadline, control.changed.signal))
|
|
35
|
+
return true;
|
|
36
|
+
if (performance.now() >= deadline) return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function exitOwnedChild(
|
|
41
|
+
child: OwnedChild,
|
|
42
|
+
control: OwnedExitControl
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
try {
|
|
45
|
+
if (!control.force) {
|
|
46
|
+
try {
|
|
47
|
+
child.send("shutdown");
|
|
48
|
+
if (
|
|
49
|
+
await waitForExit(
|
|
50
|
+
child,
|
|
51
|
+
control,
|
|
52
|
+
performance.now() + SHUTDOWN_EXIT_MS,
|
|
53
|
+
true
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
return;
|
|
57
|
+
} catch {
|
|
58
|
+
// Closed IPC still requires reaping this owned process.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
child.kill("SIGKILL");
|
|
62
|
+
if (
|
|
63
|
+
await waitForExit(
|
|
64
|
+
child,
|
|
65
|
+
control,
|
|
66
|
+
performance.now() + SHUTDOWN_EXIT_MS,
|
|
67
|
+
false
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
return;
|
|
71
|
+
} catch (cause) {
|
|
72
|
+
throw new Error(`Owned native child ${child.pid} termination failed`, {
|
|
73
|
+
cause,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Owned native child ${child.pid} did not exit before shutdown deadline`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** One retirement loop/timer, tightened in place by the resident shutdown owner. */
|
|
82
|
+
export function retireOwnedChild(
|
|
83
|
+
owner: Owner,
|
|
84
|
+
options: NativeDisposeOptions = {}
|
|
85
|
+
): Promise<void> {
|
|
86
|
+
if (owner.retirement) {
|
|
87
|
+
const control = owner.retirementControl!;
|
|
88
|
+
control.deadline = Math.min(control.deadline, options.deadline ?? Infinity);
|
|
89
|
+
control.force ||= options.force ?? false;
|
|
90
|
+
const previous = control.changed;
|
|
91
|
+
control.changed = new AbortController();
|
|
92
|
+
previous.abort();
|
|
93
|
+
return owner.retirement;
|
|
94
|
+
}
|
|
95
|
+
owner.retiring = true;
|
|
96
|
+
for (const resolve of owner.drain) resolve();
|
|
97
|
+
owner.drain.clear();
|
|
98
|
+
clearTimeout(owner.timer);
|
|
99
|
+
owner.decoder.reset();
|
|
100
|
+
const control: OwnedExitControl = {
|
|
101
|
+
deadline: options.deadline ?? performance.now() + 2 * SHUTDOWN_EXIT_MS,
|
|
102
|
+
force: options.force ?? false,
|
|
103
|
+
changed: new AbortController(),
|
|
104
|
+
};
|
|
105
|
+
owner.retirementControl = control;
|
|
106
|
+
owner.retirement = exitOwnedChild(owner.child, control);
|
|
107
|
+
return owner.retirement;
|
|
108
|
+
}
|