nemoris 0.1.0 → 0.1.2
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/.env.example +49 -49
- package/LICENSE +21 -21
- package/README.md +209 -209
- package/SECURITY.md +59 -119
- package/bin/nemoris +46 -46
- package/config/agents/agent.toml.example +28 -28
- package/config/agents/content.toml +23 -0
- package/config/agents/default.toml +22 -22
- package/config/agents/heartbeat.toml +35 -0
- package/config/agents/iris.toml +23 -0
- package/config/agents/lab.toml +23 -0
- package/config/agents/main.toml +45 -0
- package/config/agents/nemo.toml +21 -0
- package/config/agents/ops.toml +38 -0
- package/config/agents/orchestrator.toml +18 -18
- package/config/agents/revenue.toml +23 -0
- package/config/agents/testyboo.toml +19 -0
- package/config/delivery.toml +73 -73
- package/config/embeddings.toml +5 -5
- package/config/identity/content-purpose.md +11 -0
- package/config/identity/content-soul.md +45 -0
- package/config/identity/default-purpose.md +1 -1
- package/config/identity/default-soul.md +3 -3
- package/config/identity/heartbeat-purpose.md +9 -0
- package/config/identity/heartbeat-soul.md +16 -0
- package/config/identity/iris-purpose.md +17 -0
- package/config/identity/iris-soul.md +68 -0
- package/config/identity/lab-purpose.md +10 -0
- package/config/identity/lab-soul.md +38 -0
- package/config/identity/main-purpose.md +17 -0
- package/config/identity/main-soul.md +66 -0
- package/config/identity/main-user.md +22 -0
- package/config/identity/ops-purpose.md +9 -0
- package/config/identity/ops-soul.md +16 -0
- package/config/identity/orchestrator-purpose.md +1 -1
- package/config/identity/orchestrator-soul.md +1 -1
- package/config/identity/revenue-purpose.md +9 -0
- package/config/identity/revenue-soul.md +41 -0
- package/config/identity/testyboo-purpose.md +13 -0
- package/config/identity/testyboo-soul.md +20 -0
- package/config/improvement-targets.toml +15 -15
- package/config/jobs/heartbeat-check.toml +30 -30
- package/config/jobs/memory-rollup.toml +46 -46
- package/config/jobs/workspace-health.toml +63 -63
- package/config/mcp.toml +16 -16
- package/config/output-contracts.toml +17 -17
- package/config/peers.toml +32 -32
- package/config/peers.toml.example +32 -32
- package/config/policies/memory-default.toml +10 -10
- package/config/policies/memory-heartbeat.toml +5 -5
- package/config/policies/memory-ops.toml +10 -10
- package/config/policies/tools-heartbeat-minimal.toml +8 -8
- package/config/policies/tools-interactive-safe.toml +8 -8
- package/config/policies/tools-ops-bounded.toml +8 -8
- package/config/policies/tools-orchestrator.toml +7 -7
- package/config/providers/anthropic.toml +15 -15
- package/config/providers/ollama.toml +5 -5
- package/config/providers/openai-codex.toml +9 -9
- package/config/providers/openrouter.toml +5 -5
- package/config/router.toml +22 -22
- package/config/runtime.toml +114 -114
- package/config/skills/self-improvement.toml +15 -15
- package/config/skills/telegram-onboarding-spec.md +240 -240
- package/config/skills/workspace-monitor.toml +15 -15
- package/config/task-router.toml +42 -42
- package/install.sh +50 -50
- package/package.json +91 -90
- package/src/auth/auth-profiles.js +169 -169
- package/src/auth/openai-codex-oauth.js +285 -285
- package/src/battle.js +449 -449
- package/src/cli/help.js +265 -265
- package/src/cli/output-filter.js +49 -49
- package/src/cli/runtime-control.js +704 -704
- package/src/cli-main.js +2763 -2763
- package/src/cli.js +78 -78
- package/src/config/loader.js +332 -332
- package/src/config/schema-validator.js +214 -214
- package/src/config/toml-lite.js +8 -8
- package/src/daemon/action-handlers.js +71 -71
- package/src/daemon/healing-tick.js +87 -87
- package/src/daemon/health-probes.js +90 -90
- package/src/daemon/notifier.js +57 -57
- package/src/daemon/nurse.js +218 -218
- package/src/daemon/repair-log.js +106 -106
- package/src/daemon/rule-staging.js +90 -90
- package/src/daemon/rules.js +29 -29
- package/src/daemon/telegram-commands.js +54 -54
- package/src/daemon/updater.js +85 -85
- package/src/jobs/job-runner.js +78 -78
- package/src/mcp/consumer.js +129 -129
- package/src/memory/active-recall.js +171 -171
- package/src/memory/backend-manager.js +97 -97
- package/src/memory/backends/file-backend.js +38 -38
- package/src/memory/backends/qmd-backend.js +219 -219
- package/src/memory/embedding-guards.js +24 -24
- package/src/memory/embedding-index.js +118 -118
- package/src/memory/embedding-service.js +179 -179
- package/src/memory/file-index.js +177 -177
- package/src/memory/memory-signature.js +5 -5
- package/src/memory/memory-store.js +648 -648
- package/src/memory/retrieval-planner.js +66 -66
- package/src/memory/scoring.js +145 -145
- package/src/memory/simhash.js +78 -78
- package/src/memory/sqlite-active-store.js +824 -824
- package/src/memory/write-policy.js +36 -36
- package/src/onboarding/aliases.js +33 -33
- package/src/onboarding/auth/api-key.js +224 -224
- package/src/onboarding/auth/ollama-detect.js +42 -42
- package/src/onboarding/clack-prompter.js +77 -77
- package/src/onboarding/doctor.js +530 -530
- package/src/onboarding/lock.js +42 -42
- package/src/onboarding/model-catalog.js +344 -344
- package/src/onboarding/phases/auth.js +576 -589
- package/src/onboarding/phases/build.js +130 -130
- package/src/onboarding/phases/choose.js +82 -82
- package/src/onboarding/phases/detect.js +98 -98
- package/src/onboarding/phases/hatch.js +216 -216
- package/src/onboarding/phases/identity.js +79 -79
- package/src/onboarding/phases/ollama.js +345 -345
- package/src/onboarding/phases/scaffold.js +99 -99
- package/src/onboarding/phases/telegram.js +377 -377
- package/src/onboarding/phases/validate.js +204 -204
- package/src/onboarding/phases/verify.js +206 -206
- package/src/onboarding/platform.js +482 -482
- package/src/onboarding/status-bar.js +95 -95
- package/src/onboarding/templates.js +794 -794
- package/src/onboarding/toml-writer.js +38 -38
- package/src/onboarding/tui.js +250 -250
- package/src/onboarding/uninstall.js +153 -153
- package/src/onboarding/wizard.js +516 -499
- package/src/providers/anthropic.js +168 -168
- package/src/providers/base.js +247 -247
- package/src/providers/circuit-breaker.js +136 -136
- package/src/providers/ollama.js +163 -163
- package/src/providers/openai-codex.js +149 -149
- package/src/providers/openrouter.js +136 -136
- package/src/providers/registry.js +36 -36
- package/src/providers/router.js +16 -16
- package/src/runtime/bootstrap-cache.js +47 -47
- package/src/runtime/capabilities-prompt.js +25 -25
- package/src/runtime/completion-ping.js +99 -99
- package/src/runtime/config-validator.js +121 -121
- package/src/runtime/context-ledger.js +360 -360
- package/src/runtime/cutover-readiness.js +42 -42
- package/src/runtime/daemon.js +729 -729
- package/src/runtime/delivery-ack.js +195 -195
- package/src/runtime/delivery-adapters/local-file.js +41 -41
- package/src/runtime/delivery-adapters/openclaw-cli.js +94 -94
- package/src/runtime/delivery-adapters/openclaw-peer.js +98 -98
- package/src/runtime/delivery-adapters/shadow.js +13 -13
- package/src/runtime/delivery-adapters/standalone-http.js +98 -98
- package/src/runtime/delivery-adapters/telegram.js +104 -104
- package/src/runtime/delivery-adapters/tui.js +128 -128
- package/src/runtime/delivery-manager.js +807 -807
- package/src/runtime/delivery-store.js +168 -168
- package/src/runtime/dependency-health.js +118 -118
- package/src/runtime/envelope.js +114 -114
- package/src/runtime/evaluation.js +1089 -1089
- package/src/runtime/exec-approvals.js +216 -216
- package/src/runtime/executor.js +500 -500
- package/src/runtime/failure-ping.js +67 -67
- package/src/runtime/flows.js +83 -83
- package/src/runtime/guards.js +45 -45
- package/src/runtime/handoff.js +51 -51
- package/src/runtime/identity-cache.js +28 -28
- package/src/runtime/improvement-engine.js +109 -109
- package/src/runtime/improvement-harness.js +581 -581
- package/src/runtime/input-sanitiser.js +72 -72
- package/src/runtime/interaction-contract.js +347 -347
- package/src/runtime/lane-readiness.js +226 -226
- package/src/runtime/migration.js +323 -323
- package/src/runtime/model-resolution.js +78 -78
- package/src/runtime/network.js +64 -64
- package/src/runtime/notification-store.js +97 -97
- package/src/runtime/notifier.js +256 -256
- package/src/runtime/orchestrator.js +53 -53
- package/src/runtime/orphan-reaper.js +41 -41
- package/src/runtime/output-contract-schema.js +139 -139
- package/src/runtime/output-contract-validator.js +439 -439
- package/src/runtime/peer-readiness.js +69 -69
- package/src/runtime/peer-registry.js +133 -133
- package/src/runtime/pilot-status.js +108 -108
- package/src/runtime/prompt-builder.js +261 -261
- package/src/runtime/provider-attempt.js +582 -582
- package/src/runtime/report-fallback.js +71 -71
- package/src/runtime/result-normalizer.js +183 -183
- package/src/runtime/retention.js +74 -74
- package/src/runtime/review.js +244 -244
- package/src/runtime/route-job.js +15 -15
- package/src/runtime/run-store.js +38 -38
- package/src/runtime/schedule.js +88 -88
- package/src/runtime/scheduler-state.js +434 -434
- package/src/runtime/scheduler.js +656 -656
- package/src/runtime/session-compactor.js +182 -182
- package/src/runtime/session-search.js +155 -155
- package/src/runtime/slack-inbound.js +249 -249
- package/src/runtime/ssrf.js +102 -102
- package/src/runtime/status-aggregator.js +330 -330
- package/src/runtime/task-contract.js +140 -140
- package/src/runtime/task-packet.js +107 -107
- package/src/runtime/task-router.js +140 -140
- package/src/runtime/telegram-inbound.js +1565 -1565
- package/src/runtime/token-counter.js +134 -134
- package/src/runtime/token-estimator.js +59 -59
- package/src/runtime/tool-loop.js +200 -200
- package/src/runtime/transport-server.js +311 -311
- package/src/runtime/tui-server.js +411 -411
- package/src/runtime/ulid.js +44 -44
- package/src/security/ssrf-check.js +197 -197
- package/src/setup.js +369 -369
- package/src/shadow/bridge.js +303 -303
- package/src/skills/loader.js +84 -84
- package/src/tools/catalog.json +49 -49
- package/src/tools/cli-delegate.js +44 -44
- package/src/tools/mcp-client.js +106 -106
- package/src/tools/micro/cancel-task.js +6 -6
- package/src/tools/micro/complete-task.js +6 -6
- package/src/tools/micro/fail-task.js +6 -6
- package/src/tools/micro/http-fetch.js +74 -74
- package/src/tools/micro/index.js +36 -36
- package/src/tools/micro/lcm-recall.js +60 -60
- package/src/tools/micro/list-dir.js +17 -17
- package/src/tools/micro/list-skills.js +46 -46
- package/src/tools/micro/load-skill.js +38 -38
- package/src/tools/micro/memory-search.js +45 -45
- package/src/tools/micro/read-file.js +11 -11
- package/src/tools/micro/session-search.js +54 -54
- package/src/tools/micro/shell-exec.js +43 -43
- package/src/tools/micro/trigger-job.js +79 -79
- package/src/tools/micro/web-search.js +58 -58
- package/src/tools/micro/workspace-paths.js +39 -39
- package/src/tools/micro/write-file.js +14 -14
- package/src/tools/micro/write-memory.js +41 -41
- package/src/tools/registry.js +348 -348
- package/src/tools/tool-result-contract.js +36 -36
- package/src/tui/chat.js +835 -835
- package/src/tui/renderer.js +175 -175
- package/src/tui/socket-client.js +217 -217
- package/src/utils/canonical-json.js +29 -29
- package/src/utils/compaction.js +30 -30
- package/src/utils/env-loader.js +5 -5
- package/src/utils/errors.js +80 -80
- package/src/utils/fs.js +101 -101
- package/src/utils/ids.js +5 -5
- package/src/utils/model-context-limits.js +30 -30
- package/src/utils/token-budget.js +74 -74
- package/src/utils/usage-cost.js +25 -25
- package/src/utils/usage-metrics.js +14 -14
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { ensureDir } from "../utils/fs.js";
|
|
3
|
-
import { buildResultSignature } from "./memory-signature.js";
|
|
4
|
-
import { SqliteActiveStore } from "./sqlite-active-store.js";
|
|
5
|
-
|
|
6
|
-
export class EmbeddingIndex {
|
|
7
|
-
constructor({ rootDir, embeddingService = null } = {}) {
|
|
8
|
-
this.rootDir = rootDir;
|
|
9
|
-
this.embeddingService = embeddingService;
|
|
10
|
-
this.stores = new Map();
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
dbPath(agentId) {
|
|
14
|
-
return path.join(this.rootDir, agentId, "memory.sqlite");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async getStore(agentId) {
|
|
18
|
-
const dbPath = this.dbPath(agentId);
|
|
19
|
-
await ensureDir(path.dirname(dbPath));
|
|
20
|
-
if (!this.stores.has(dbPath)) {
|
|
21
|
-
this.stores.set(dbPath, new SqliteActiveStore({ dbPath }));
|
|
22
|
-
}
|
|
23
|
-
return this.stores.get(dbPath);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async load(agentId) {
|
|
27
|
-
const store = await this.getStore(agentId);
|
|
28
|
-
return {
|
|
29
|
-
agentId,
|
|
30
|
-
...store.getEmbeddingIndex()
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async save(agentId, payload) {
|
|
35
|
-
const store = await this.getStore(agentId);
|
|
36
|
-
return {
|
|
37
|
-
agentId,
|
|
38
|
-
...store.replaceEmbeddings(payload)
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async rebuild(agentId, memoryItems) {
|
|
43
|
-
if (!this.embeddingService) {
|
|
44
|
-
throw new Error("No embedding service configured");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const texts = memoryItems.map((item) =>
|
|
48
|
-
[item.title, item.summary, item.content, item.reason, item.category].filter(Boolean).join("\n")
|
|
49
|
-
);
|
|
50
|
-
const embedded = await this.embeddingService.embedTexts(texts);
|
|
51
|
-
const items = memoryItems.map((item, index) => ({
|
|
52
|
-
entryId: item.entryId || `synthetic:${index}:${buildResultSignature(item)}`,
|
|
53
|
-
signature: buildResultSignature(item),
|
|
54
|
-
item,
|
|
55
|
-
vector: embedded.vectors[index] || []
|
|
56
|
-
}));
|
|
57
|
-
|
|
58
|
-
return this.save(agentId, {
|
|
59
|
-
agentId,
|
|
60
|
-
updatedAt: new Date().toISOString(),
|
|
61
|
-
config: embedded.config,
|
|
62
|
-
items
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async query(agentId, queryText, limit = 8) {
|
|
67
|
-
const store = await this.getStore(agentId);
|
|
68
|
-
const index = store.getEmbeddingIndex();
|
|
69
|
-
if (!this.embeddingService || !index.items?.length) {
|
|
70
|
-
return [];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const embedded = await this.embedQuery(queryText);
|
|
74
|
-
const queryVector = embedded.vector;
|
|
75
|
-
if (!Array.isArray(queryVector)) return [];
|
|
76
|
-
|
|
77
|
-
const sqliteResults = store.queryEmbeddingSimilarities(queryVector, limit);
|
|
78
|
-
if (sqliteResults.length) {
|
|
79
|
-
return sqliteResults;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return index.items
|
|
83
|
-
.map((entry) => ({
|
|
84
|
-
entryId: entry.entryId || null,
|
|
85
|
-
signature: entry.signature,
|
|
86
|
-
similarity: cosineSimilarity(queryVector, entry.vector || []),
|
|
87
|
-
freshnessStatus: entry.freshnessStatus || "fresh",
|
|
88
|
-
item: entry.item
|
|
89
|
-
}))
|
|
90
|
-
.sort((a, b) => b.similarity - a.similarity)
|
|
91
|
-
.slice(0, limit);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async embedQuery(queryText) {
|
|
95
|
-
if (!this.embeddingService) {
|
|
96
|
-
throw new Error("No embedding service configured");
|
|
97
|
-
}
|
|
98
|
-
const embedded = await this.embeddingService.embedTexts([queryText]);
|
|
99
|
-
return {
|
|
100
|
-
vector: embedded.vectors[0],
|
|
101
|
-
config: embedded.config || null
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function cosineSimilarity(a = [], b = []) {
|
|
107
|
-
let dot = 0;
|
|
108
|
-
let normA = 0;
|
|
109
|
-
let normB = 0;
|
|
110
|
-
const length = Math.min(a.length, b.length);
|
|
111
|
-
for (let i = 0; i < length; i += 1) {
|
|
112
|
-
dot += a[i] * b[i];
|
|
113
|
-
normA += a[i] * a[i];
|
|
114
|
-
normB += b[i] * b[i];
|
|
115
|
-
}
|
|
116
|
-
if (normA === 0 || normB === 0) return 0;
|
|
117
|
-
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
118
|
-
}
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { ensureDir } from "../utils/fs.js";
|
|
3
|
+
import { buildResultSignature } from "./memory-signature.js";
|
|
4
|
+
import { SqliteActiveStore } from "./sqlite-active-store.js";
|
|
5
|
+
|
|
6
|
+
export class EmbeddingIndex {
|
|
7
|
+
constructor({ rootDir, embeddingService = null } = {}) {
|
|
8
|
+
this.rootDir = rootDir;
|
|
9
|
+
this.embeddingService = embeddingService;
|
|
10
|
+
this.stores = new Map();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
dbPath(agentId) {
|
|
14
|
+
return path.join(this.rootDir, agentId, "memory.sqlite");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async getStore(agentId) {
|
|
18
|
+
const dbPath = this.dbPath(agentId);
|
|
19
|
+
await ensureDir(path.dirname(dbPath));
|
|
20
|
+
if (!this.stores.has(dbPath)) {
|
|
21
|
+
this.stores.set(dbPath, new SqliteActiveStore({ dbPath }));
|
|
22
|
+
}
|
|
23
|
+
return this.stores.get(dbPath);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async load(agentId) {
|
|
27
|
+
const store = await this.getStore(agentId);
|
|
28
|
+
return {
|
|
29
|
+
agentId,
|
|
30
|
+
...store.getEmbeddingIndex()
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async save(agentId, payload) {
|
|
35
|
+
const store = await this.getStore(agentId);
|
|
36
|
+
return {
|
|
37
|
+
agentId,
|
|
38
|
+
...store.replaceEmbeddings(payload)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async rebuild(agentId, memoryItems) {
|
|
43
|
+
if (!this.embeddingService) {
|
|
44
|
+
throw new Error("No embedding service configured");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const texts = memoryItems.map((item) =>
|
|
48
|
+
[item.title, item.summary, item.content, item.reason, item.category].filter(Boolean).join("\n")
|
|
49
|
+
);
|
|
50
|
+
const embedded = await this.embeddingService.embedTexts(texts);
|
|
51
|
+
const items = memoryItems.map((item, index) => ({
|
|
52
|
+
entryId: item.entryId || `synthetic:${index}:${buildResultSignature(item)}`,
|
|
53
|
+
signature: buildResultSignature(item),
|
|
54
|
+
item,
|
|
55
|
+
vector: embedded.vectors[index] || []
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
return this.save(agentId, {
|
|
59
|
+
agentId,
|
|
60
|
+
updatedAt: new Date().toISOString(),
|
|
61
|
+
config: embedded.config,
|
|
62
|
+
items
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async query(agentId, queryText, limit = 8) {
|
|
67
|
+
const store = await this.getStore(agentId);
|
|
68
|
+
const index = store.getEmbeddingIndex();
|
|
69
|
+
if (!this.embeddingService || !index.items?.length) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const embedded = await this.embedQuery(queryText);
|
|
74
|
+
const queryVector = embedded.vector;
|
|
75
|
+
if (!Array.isArray(queryVector)) return [];
|
|
76
|
+
|
|
77
|
+
const sqliteResults = store.queryEmbeddingSimilarities(queryVector, limit);
|
|
78
|
+
if (sqliteResults.length) {
|
|
79
|
+
return sqliteResults;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return index.items
|
|
83
|
+
.map((entry) => ({
|
|
84
|
+
entryId: entry.entryId || null,
|
|
85
|
+
signature: entry.signature,
|
|
86
|
+
similarity: cosineSimilarity(queryVector, entry.vector || []),
|
|
87
|
+
freshnessStatus: entry.freshnessStatus || "fresh",
|
|
88
|
+
item: entry.item
|
|
89
|
+
}))
|
|
90
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
91
|
+
.slice(0, limit);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async embedQuery(queryText) {
|
|
95
|
+
if (!this.embeddingService) {
|
|
96
|
+
throw new Error("No embedding service configured");
|
|
97
|
+
}
|
|
98
|
+
const embedded = await this.embeddingService.embedTexts([queryText]);
|
|
99
|
+
return {
|
|
100
|
+
vector: embedded.vectors[0],
|
|
101
|
+
config: embedded.config || null
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function cosineSimilarity(a = [], b = []) {
|
|
107
|
+
let dot = 0;
|
|
108
|
+
let normA = 0;
|
|
109
|
+
let normB = 0;
|
|
110
|
+
const length = Math.min(a.length, b.length);
|
|
111
|
+
for (let i = 0; i < length; i += 1) {
|
|
112
|
+
dot += a[i] * b[i];
|
|
113
|
+
normA += a[i] * a[i];
|
|
114
|
+
normB += b[i] * b[i];
|
|
115
|
+
}
|
|
116
|
+
if (normA === 0 || normB === 0) return 0;
|
|
117
|
+
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
118
|
+
}
|
|
@@ -1,179 +1,179 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { ConfigLoader } from "../config/loader.js";
|
|
3
|
-
import { ProviderRegistry } from "../providers/registry.js";
|
|
4
|
-
import { assertEmbeddingsAllowed, getEmbeddingPolicy } from "./embedding-guards.js";
|
|
5
|
-
|
|
6
|
-
function modelToProviderId(modelId) {
|
|
7
|
-
return String(modelId || "").split("/")[0] || null;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function normalizeOllamaModelName(modelId) {
|
|
11
|
-
const normalized = String(modelId || "").replace(/^ollama\//, "");
|
|
12
|
-
return normalized;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function matchesAvailableModel(targetModel, availableModels) {
|
|
16
|
-
const target = normalizeOllamaModelName(targetModel);
|
|
17
|
-
if (!target || !Array.isArray(availableModels)) return false;
|
|
18
|
-
return availableModels.some((model) => {
|
|
19
|
-
const candidate = normalizeOllamaModelName(model);
|
|
20
|
-
return candidate === target || candidate === `${target}:latest`;
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class EmbeddingService {
|
|
25
|
-
constructor({ projectRoot, fetchImpl } = {}) {
|
|
26
|
-
this.projectRoot = projectRoot;
|
|
27
|
-
this.loader = new ConfigLoader({ rootDir: path.join(projectRoot, "config") });
|
|
28
|
-
this.registry = new ProviderRegistry({ fetchImpl });
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async loadConfig() {
|
|
32
|
-
const raw = await this.loader.loadEmbeddings();
|
|
33
|
-
return {
|
|
34
|
-
enabled: raw.enabled ?? false,
|
|
35
|
-
provider: raw.provider || modelToProviderId(raw.model),
|
|
36
|
-
model: raw.model || null,
|
|
37
|
-
dimensions: raw.dimensions ?? 128,
|
|
38
|
-
indexOnWrite: raw.indexOnWrite ?? false
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async embedTexts(texts) {
|
|
43
|
-
const config = await this.loadConfig();
|
|
44
|
-
const providerId = config.provider || modelToProviderId(config.model);
|
|
45
|
-
const policy = assertEmbeddingsAllowed(providerId);
|
|
46
|
-
const providers = await this.loader.loadProviders();
|
|
47
|
-
const providerConfig = providers[providerId];
|
|
48
|
-
if (!providerConfig) {
|
|
49
|
-
throw new Error(`No provider config for embedding provider ${providerId}`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const adapter = this.registry.create(providerConfig);
|
|
53
|
-
if (policy.requireHealthyProvider) {
|
|
54
|
-
const health = await adapter.healthCheck();
|
|
55
|
-
if (!health.ok) {
|
|
56
|
-
throw new Error(`Embedding provider health check failed for ${providerId} with status ${health.status}`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
if (typeof adapter.embed !== "function") {
|
|
60
|
-
throw new Error(`Provider ${providerId} does not implement embeddings`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const result = await adapter.embed({
|
|
64
|
-
model: config.model,
|
|
65
|
-
input: texts
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
config,
|
|
70
|
-
policy: getEmbeddingPolicy(),
|
|
71
|
-
vectors: result.embeddings || []
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async getReadiness() {
|
|
76
|
-
const config = await this.loadConfig();
|
|
77
|
-
const providerId = config.provider || modelToProviderId(config.model);
|
|
78
|
-
const policy = getEmbeddingPolicy();
|
|
79
|
-
|
|
80
|
-
if (!policy.allowEmbeddings) {
|
|
81
|
-
return {
|
|
82
|
-
ready: false,
|
|
83
|
-
config,
|
|
84
|
-
policy,
|
|
85
|
-
providerId,
|
|
86
|
-
reason: "Embeddings disabled. Set NEMORIS_ALLOW_EMBEDDINGS=1 to enable embedding indexing."
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (providerId !== policy.allowedProvider && !policy.allowRemoteEmbeddings) {
|
|
91
|
-
return {
|
|
92
|
-
ready: false,
|
|
93
|
-
config,
|
|
94
|
-
policy,
|
|
95
|
-
providerId,
|
|
96
|
-
reason: `Embedding provider ${providerId} is blocked by policy.`
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const providers = await this.loader.loadProviders();
|
|
101
|
-
const providerConfig = providers[providerId];
|
|
102
|
-
if (!providerConfig) {
|
|
103
|
-
return {
|
|
104
|
-
ready: false,
|
|
105
|
-
config,
|
|
106
|
-
policy,
|
|
107
|
-
providerId,
|
|
108
|
-
reason: `No provider config for embedding provider ${providerId}`
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const adapter = this.registry.create(providerConfig);
|
|
113
|
-
const health = await adapter.healthCheck();
|
|
114
|
-
if (!health.ok) {
|
|
115
|
-
return {
|
|
116
|
-
ready: false,
|
|
117
|
-
config,
|
|
118
|
-
policy,
|
|
119
|
-
providerId,
|
|
120
|
-
providerHealth: health,
|
|
121
|
-
reason: `Embedding provider health check failed for ${providerId} with status ${health.status}`
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (typeof adapter.embed !== "function") {
|
|
126
|
-
return {
|
|
127
|
-
ready: false,
|
|
128
|
-
config,
|
|
129
|
-
policy,
|
|
130
|
-
providerId,
|
|
131
|
-
providerHealth: health,
|
|
132
|
-
reason: `Provider ${providerId} does not implement embeddings`
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
let availableModels = null;
|
|
137
|
-
let modelAvailable = null;
|
|
138
|
-
if (typeof adapter.listModels === "function") {
|
|
139
|
-
try {
|
|
140
|
-
availableModels = await adapter.listModels();
|
|
141
|
-
if (Array.isArray(availableModels) && config.model) {
|
|
142
|
-
const normalizedTarget = normalizeOllamaModelName(config.model);
|
|
143
|
-
modelAvailable = matchesAvailableModel(config.model, availableModels);
|
|
144
|
-
if (!modelAvailable) {
|
|
145
|
-
return {
|
|
146
|
-
ready: false,
|
|
147
|
-
config,
|
|
148
|
-
policy,
|
|
149
|
-
providerId,
|
|
150
|
-
providerHealth: health,
|
|
151
|
-
availableModels,
|
|
152
|
-
modelAvailable,
|
|
153
|
-
reason: `Embedding model ${normalizedTarget} is not available in ${providerId}`
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
} catch (error) {
|
|
158
|
-
return {
|
|
159
|
-
ready: false,
|
|
160
|
-
config,
|
|
161
|
-
policy,
|
|
162
|
-
providerId,
|
|
163
|
-
providerHealth: health,
|
|
164
|
-
reason: error?.message || String(error)
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return {
|
|
170
|
-
ready: true,
|
|
171
|
-
config,
|
|
172
|
-
policy,
|
|
173
|
-
providerId,
|
|
174
|
-
providerHealth: health,
|
|
175
|
-
availableModels,
|
|
176
|
-
modelAvailable
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
}
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { ConfigLoader } from "../config/loader.js";
|
|
3
|
+
import { ProviderRegistry } from "../providers/registry.js";
|
|
4
|
+
import { assertEmbeddingsAllowed, getEmbeddingPolicy } from "./embedding-guards.js";
|
|
5
|
+
|
|
6
|
+
function modelToProviderId(modelId) {
|
|
7
|
+
return String(modelId || "").split("/")[0] || null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function normalizeOllamaModelName(modelId) {
|
|
11
|
+
const normalized = String(modelId || "").replace(/^ollama\//, "");
|
|
12
|
+
return normalized;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function matchesAvailableModel(targetModel, availableModels) {
|
|
16
|
+
const target = normalizeOllamaModelName(targetModel);
|
|
17
|
+
if (!target || !Array.isArray(availableModels)) return false;
|
|
18
|
+
return availableModels.some((model) => {
|
|
19
|
+
const candidate = normalizeOllamaModelName(model);
|
|
20
|
+
return candidate === target || candidate === `${target}:latest`;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class EmbeddingService {
|
|
25
|
+
constructor({ projectRoot, fetchImpl } = {}) {
|
|
26
|
+
this.projectRoot = projectRoot;
|
|
27
|
+
this.loader = new ConfigLoader({ rootDir: path.join(projectRoot, "config") });
|
|
28
|
+
this.registry = new ProviderRegistry({ fetchImpl });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async loadConfig() {
|
|
32
|
+
const raw = await this.loader.loadEmbeddings();
|
|
33
|
+
return {
|
|
34
|
+
enabled: raw.enabled ?? false,
|
|
35
|
+
provider: raw.provider || modelToProviderId(raw.model),
|
|
36
|
+
model: raw.model || null,
|
|
37
|
+
dimensions: raw.dimensions ?? 128,
|
|
38
|
+
indexOnWrite: raw.indexOnWrite ?? false
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async embedTexts(texts) {
|
|
43
|
+
const config = await this.loadConfig();
|
|
44
|
+
const providerId = config.provider || modelToProviderId(config.model);
|
|
45
|
+
const policy = assertEmbeddingsAllowed(providerId);
|
|
46
|
+
const providers = await this.loader.loadProviders();
|
|
47
|
+
const providerConfig = providers[providerId];
|
|
48
|
+
if (!providerConfig) {
|
|
49
|
+
throw new Error(`No provider config for embedding provider ${providerId}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const adapter = this.registry.create(providerConfig);
|
|
53
|
+
if (policy.requireHealthyProvider) {
|
|
54
|
+
const health = await adapter.healthCheck();
|
|
55
|
+
if (!health.ok) {
|
|
56
|
+
throw new Error(`Embedding provider health check failed for ${providerId} with status ${health.status}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (typeof adapter.embed !== "function") {
|
|
60
|
+
throw new Error(`Provider ${providerId} does not implement embeddings`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const result = await adapter.embed({
|
|
64
|
+
model: config.model,
|
|
65
|
+
input: texts
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
config,
|
|
70
|
+
policy: getEmbeddingPolicy(),
|
|
71
|
+
vectors: result.embeddings || []
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async getReadiness() {
|
|
76
|
+
const config = await this.loadConfig();
|
|
77
|
+
const providerId = config.provider || modelToProviderId(config.model);
|
|
78
|
+
const policy = getEmbeddingPolicy();
|
|
79
|
+
|
|
80
|
+
if (!policy.allowEmbeddings) {
|
|
81
|
+
return {
|
|
82
|
+
ready: false,
|
|
83
|
+
config,
|
|
84
|
+
policy,
|
|
85
|
+
providerId,
|
|
86
|
+
reason: "Embeddings disabled. Set NEMORIS_ALLOW_EMBEDDINGS=1 to enable embedding indexing."
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (providerId !== policy.allowedProvider && !policy.allowRemoteEmbeddings) {
|
|
91
|
+
return {
|
|
92
|
+
ready: false,
|
|
93
|
+
config,
|
|
94
|
+
policy,
|
|
95
|
+
providerId,
|
|
96
|
+
reason: `Embedding provider ${providerId} is blocked by policy.`
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const providers = await this.loader.loadProviders();
|
|
101
|
+
const providerConfig = providers[providerId];
|
|
102
|
+
if (!providerConfig) {
|
|
103
|
+
return {
|
|
104
|
+
ready: false,
|
|
105
|
+
config,
|
|
106
|
+
policy,
|
|
107
|
+
providerId,
|
|
108
|
+
reason: `No provider config for embedding provider ${providerId}`
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const adapter = this.registry.create(providerConfig);
|
|
113
|
+
const health = await adapter.healthCheck();
|
|
114
|
+
if (!health.ok) {
|
|
115
|
+
return {
|
|
116
|
+
ready: false,
|
|
117
|
+
config,
|
|
118
|
+
policy,
|
|
119
|
+
providerId,
|
|
120
|
+
providerHealth: health,
|
|
121
|
+
reason: `Embedding provider health check failed for ${providerId} with status ${health.status}`
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (typeof adapter.embed !== "function") {
|
|
126
|
+
return {
|
|
127
|
+
ready: false,
|
|
128
|
+
config,
|
|
129
|
+
policy,
|
|
130
|
+
providerId,
|
|
131
|
+
providerHealth: health,
|
|
132
|
+
reason: `Provider ${providerId} does not implement embeddings`
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let availableModels = null;
|
|
137
|
+
let modelAvailable = null;
|
|
138
|
+
if (typeof adapter.listModels === "function") {
|
|
139
|
+
try {
|
|
140
|
+
availableModels = await adapter.listModels();
|
|
141
|
+
if (Array.isArray(availableModels) && config.model) {
|
|
142
|
+
const normalizedTarget = normalizeOllamaModelName(config.model);
|
|
143
|
+
modelAvailable = matchesAvailableModel(config.model, availableModels);
|
|
144
|
+
if (!modelAvailable) {
|
|
145
|
+
return {
|
|
146
|
+
ready: false,
|
|
147
|
+
config,
|
|
148
|
+
policy,
|
|
149
|
+
providerId,
|
|
150
|
+
providerHealth: health,
|
|
151
|
+
availableModels,
|
|
152
|
+
modelAvailable,
|
|
153
|
+
reason: `Embedding model ${normalizedTarget} is not available in ${providerId}`
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
return {
|
|
159
|
+
ready: false,
|
|
160
|
+
config,
|
|
161
|
+
policy,
|
|
162
|
+
providerId,
|
|
163
|
+
providerHealth: health,
|
|
164
|
+
reason: error?.message || String(error)
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
ready: true,
|
|
171
|
+
config,
|
|
172
|
+
policy,
|
|
173
|
+
providerId,
|
|
174
|
+
providerHealth: health,
|
|
175
|
+
availableModels,
|
|
176
|
+
modelAvailable
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|