agentikit 0.0.13 → 0.0.15
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 +385 -0
- package/README.md +187 -110
- package/dist/{src/asset-spec.js → asset-spec.js} +11 -2
- package/dist/{src/asset-type-handler.js → asset-type-handler.js} +4 -3
- package/dist/cli.js +709 -0
- package/dist/common.js +192 -0
- package/dist/{src/config-cli.js → config-cli.js} +36 -30
- package/dist/{src/config.js → config.js} +95 -25
- package/dist/{src/db.js → db.js} +123 -51
- package/dist/{src/embedder.js → embedder.js} +57 -2
- package/dist/errors.js +28 -0
- package/dist/file-context.js +188 -0
- package/dist/{src/frontmatter.js → frontmatter.js} +1 -1
- package/dist/{src/github.js → github.js} +1 -3
- package/dist/handlers/agent-handler.js +19 -0
- package/dist/handlers/command-handler.js +20 -0
- package/dist/handlers/handler-bridge.js +51 -0
- package/dist/handlers/index.js +19 -0
- package/dist/handlers/knowledge-handler.js +32 -0
- package/dist/handlers/script-handler.js +42 -0
- package/dist/{src/handlers → handlers}/skill-handler.js +5 -6
- package/dist/{src/handlers → handlers}/tool-handler.js +8 -24
- package/dist/{src/indexer.js → indexer.js} +50 -26
- package/dist/init.js +43 -0
- package/dist/{src/llm.js → llm.js} +6 -11
- package/dist/lockfile.js +60 -0
- package/dist/matchers.js +163 -0
- package/dist/{src/metadata.js → metadata.js} +36 -16
- package/dist/{src/origin-resolve.js → origin-resolve.js} +10 -9
- package/dist/paths.js +83 -0
- package/dist/{src/registry-install.js → registry-install.js} +151 -19
- package/dist/{src/registry-resolve.js → registry-resolve.js} +190 -26
- package/dist/{src/registry-search.js → registry-search.js} +13 -21
- package/dist/renderers.js +286 -0
- package/dist/{src/ripgrep-install.js → ripgrep-install.js} +8 -27
- package/dist/{src/ripgrep-resolve.js → ripgrep-resolve.js} +21 -11
- package/dist/ripgrep.js +2 -0
- package/dist/self-update.js +226 -0
- package/dist/{src/stash-add.js → stash-add.js} +14 -4
- package/dist/stash-clone.js +115 -0
- package/dist/{src/stash-ref.js → stash-ref.js} +10 -9
- package/dist/{src/stash-registry.js → stash-registry.js} +21 -46
- package/dist/{src/stash-resolve.js → stash-resolve.js} +10 -9
- package/dist/{src/stash-search.js → stash-search.js} +89 -74
- package/dist/stash-show.js +74 -0
- package/dist/stash-source.js +127 -0
- package/dist/submit.js +557 -0
- package/dist/{src/tool-runner.js → tool-runner.js} +1 -5
- package/dist/{src/walker.js → walker.js} +38 -0
- package/dist/warn.js +20 -0
- package/package.json +13 -18
- package/dist/index.d.ts +0 -28
- package/dist/index.js +0 -15
- package/dist/src/asset-spec.d.ts +0 -16
- package/dist/src/asset-type-handler.d.ts +0 -27
- package/dist/src/cli.d.ts +0 -2
- package/dist/src/cli.js +0 -399
- package/dist/src/common.d.ts +0 -13
- package/dist/src/common.js +0 -60
- package/dist/src/config-cli.d.ts +0 -9
- package/dist/src/config.d.ts +0 -50
- package/dist/src/db.d.ts +0 -46
- package/dist/src/embedder.d.ts +0 -10
- package/dist/src/frontmatter.d.ts +0 -30
- package/dist/src/github.d.ts +0 -4
- package/dist/src/handlers/agent-handler.d.ts +0 -2
- package/dist/src/handlers/agent-handler.js +0 -26
- package/dist/src/handlers/command-handler.d.ts +0 -2
- package/dist/src/handlers/command-handler.js +0 -23
- package/dist/src/handlers/index.d.ts +0 -6
- package/dist/src/handlers/index.js +0 -23
- package/dist/src/handlers/knowledge-handler.d.ts +0 -2
- package/dist/src/handlers/knowledge-handler.js +0 -56
- package/dist/src/handlers/markdown-helpers.d.ts +0 -7
- package/dist/src/handlers/script-handler.d.ts +0 -2
- package/dist/src/handlers/script-handler.js +0 -78
- package/dist/src/handlers/skill-handler.d.ts +0 -2
- package/dist/src/handlers/tool-handler.d.ts +0 -2
- package/dist/src/indexer.d.ts +0 -22
- package/dist/src/init.d.ts +0 -19
- package/dist/src/init.js +0 -99
- package/dist/src/llm.d.ts +0 -15
- package/dist/src/markdown.d.ts +0 -18
- package/dist/src/metadata.d.ts +0 -41
- package/dist/src/origin-resolve.d.ts +0 -19
- package/dist/src/registry-install.d.ts +0 -11
- package/dist/src/registry-resolve.d.ts +0 -3
- package/dist/src/registry-search.d.ts +0 -27
- package/dist/src/registry-types.d.ts +0 -62
- package/dist/src/ripgrep-install.d.ts +0 -12
- package/dist/src/ripgrep-resolve.d.ts +0 -13
- package/dist/src/ripgrep.d.ts +0 -3
- package/dist/src/ripgrep.js +0 -2
- package/dist/src/stash-add.d.ts +0 -4
- package/dist/src/stash-clone.d.ts +0 -22
- package/dist/src/stash-clone.js +0 -83
- package/dist/src/stash-ref.d.ts +0 -31
- package/dist/src/stash-registry.d.ts +0 -18
- package/dist/src/stash-resolve.d.ts +0 -2
- package/dist/src/stash-search.d.ts +0 -8
- package/dist/src/stash-show.d.ts +0 -5
- package/dist/src/stash-show.js +0 -46
- package/dist/src/stash-source.d.ts +0 -24
- package/dist/src/stash-source.js +0 -81
- package/dist/src/stash-types.d.ts +0 -227
- package/dist/src/stash.d.ts +0 -16
- package/dist/src/stash.js +0 -9
- package/dist/src/tool-runner.d.ts +0 -35
- package/dist/src/walker.d.ts +0 -19
- package/src/asset-spec.ts +0 -85
- package/src/asset-type-handler.ts +0 -77
- package/src/cli.ts +0 -427
- package/src/common.ts +0 -76
- package/src/config-cli.ts +0 -499
- package/src/config.ts +0 -305
- package/src/db.ts +0 -411
- package/src/embedder.ts +0 -128
- package/src/frontmatter.ts +0 -95
- package/src/github.ts +0 -21
- package/src/handlers/agent-handler.ts +0 -32
- package/src/handlers/command-handler.ts +0 -29
- package/src/handlers/index.ts +0 -25
- package/src/handlers/knowledge-handler.ts +0 -62
- package/src/handlers/markdown-helpers.ts +0 -19
- package/src/handlers/script-handler.ts +0 -92
- package/src/handlers/skill-handler.ts +0 -37
- package/src/handlers/tool-handler.ts +0 -71
- package/src/indexer.ts +0 -392
- package/src/init.ts +0 -114
- package/src/llm.ts +0 -125
- package/src/markdown.ts +0 -106
- package/src/metadata.ts +0 -333
- package/src/origin-resolve.ts +0 -67
- package/src/registry-install.ts +0 -361
- package/src/registry-resolve.ts +0 -341
- package/src/registry-search.ts +0 -335
- package/src/registry-types.ts +0 -72
- package/src/ripgrep-install.ts +0 -200
- package/src/ripgrep-resolve.ts +0 -72
- package/src/ripgrep.ts +0 -3
- package/src/stash-add.ts +0 -63
- package/src/stash-clone.ts +0 -127
- package/src/stash-ref.ts +0 -99
- package/src/stash-registry.ts +0 -259
- package/src/stash-resolve.ts +0 -50
- package/src/stash-search.ts +0 -613
- package/src/stash-show.ts +0 -55
- package/src/stash-source.ts +0 -103
- package/src/stash-types.ts +0 -231
- package/src/stash.ts +0 -39
- package/src/tool-runner.ts +0 -142
- package/src/walker.ts +0 -53
- /package/dist/{src/handlers → handlers}/markdown-helpers.js +0 -0
- /package/dist/{src/markdown.js → markdown.js} +0 -0
- /package/dist/{src/registry-types.js → registry-types.js} +0 -0
- /package/dist/{src/stash-types.js → stash-types.js} +0 -0
package/dist/{src/db.js → db.js}
RENAMED
|
@@ -1,15 +1,13 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
1
2
|
import fs from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
2
4
|
import path from "node:path";
|
|
3
|
-
import {
|
|
5
|
+
import { cosineSimilarity } from "./embedder";
|
|
6
|
+
import { getDbPath } from "./paths";
|
|
7
|
+
import { warn } from "./warn";
|
|
4
8
|
// ── Constants ───────────────────────────────────────────────────────────────
|
|
5
|
-
export const DB_VERSION =
|
|
9
|
+
export const DB_VERSION = 6;
|
|
6
10
|
export const EMBEDDING_DIM = 384;
|
|
7
|
-
// ── Path ────────────────────────────────────────────────────────────────────
|
|
8
|
-
export function getDbPath() {
|
|
9
|
-
const cacheDir = process.env.XDG_CACHE_HOME ||
|
|
10
|
-
path.join(process.env.HOME || process.env.USERPROFILE || "", ".cache");
|
|
11
|
-
return path.join(cacheDir, "agentikit", "index.db");
|
|
12
|
-
}
|
|
13
11
|
// ── Database lifecycle ──────────────────────────────────────────────────────
|
|
14
12
|
export function openDatabase(dbPath, options) {
|
|
15
13
|
const resolvedPath = dbPath ?? getDbPath();
|
|
@@ -23,26 +21,53 @@ export function openDatabase(dbPath, options) {
|
|
|
23
21
|
// Try to load sqlite-vec extension
|
|
24
22
|
loadVecExtension(db);
|
|
25
23
|
ensureSchema(db, options?.embeddingDim ?? EMBEDDING_DIM);
|
|
24
|
+
// Warn once at init if using JS fallback with many entries
|
|
25
|
+
warnIfVecMissing(db, { once: true });
|
|
26
26
|
return db;
|
|
27
27
|
}
|
|
28
28
|
export function closeDatabase(db) {
|
|
29
29
|
db.close();
|
|
30
30
|
}
|
|
31
31
|
// ── sqlite-vec extension ────────────────────────────────────────────────────
|
|
32
|
-
|
|
32
|
+
const vecStatus = new WeakMap();
|
|
33
33
|
function loadVecExtension(db) {
|
|
34
34
|
try {
|
|
35
|
-
const
|
|
35
|
+
const esmRequire = createRequire(import.meta.url);
|
|
36
|
+
const sqliteVec = esmRequire("sqlite-vec");
|
|
36
37
|
sqliteVec.load(db);
|
|
37
|
-
|
|
38
|
+
vecStatus.set(db, true);
|
|
38
39
|
}
|
|
39
40
|
catch {
|
|
40
|
-
|
|
41
|
-
vecAvailable = false;
|
|
41
|
+
vecStatus.set(db, false);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
export function isVecAvailable() {
|
|
45
|
-
return
|
|
44
|
+
export function isVecAvailable(db) {
|
|
45
|
+
return vecStatus.get(db) ?? false;
|
|
46
|
+
}
|
|
47
|
+
const VEC_DOCS_URL = "https://github.com/itlackey/agentikit/blob/main/docs/configuration.md#sqlite-vec-extension";
|
|
48
|
+
const VEC_FALLBACK_THRESHOLD = 10_000;
|
|
49
|
+
let vecInitWarned = false;
|
|
50
|
+
/**
|
|
51
|
+
* Warn if sqlite-vec is unavailable and embedding count exceeds threshold.
|
|
52
|
+
* Called from openDatabase (once at init) and from indexer (each run).
|
|
53
|
+
*/
|
|
54
|
+
export function warnIfVecMissing(db, { once } = { once: false }) {
|
|
55
|
+
if (isVecAvailable(db))
|
|
56
|
+
return;
|
|
57
|
+
if (once && vecInitWarned)
|
|
58
|
+
return;
|
|
59
|
+
try {
|
|
60
|
+
const row = db.prepare("SELECT COUNT(*) AS cnt FROM embeddings").get();
|
|
61
|
+
const count = row?.cnt ?? 0;
|
|
62
|
+
if (count >= VEC_FALLBACK_THRESHOLD) {
|
|
63
|
+
warn("Semantic search is using JS fallback for %d entries. Install sqlite-vec for faster performance.\n See: %s", count, VEC_DOCS_URL);
|
|
64
|
+
if (once)
|
|
65
|
+
vecInitWarned = true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
/* embeddings table may not exist yet during init */
|
|
70
|
+
}
|
|
46
71
|
}
|
|
47
72
|
// ── Schema ──────────────────────────────────────────────────────────────────
|
|
48
73
|
function ensureSchema(db, embeddingDim) {
|
|
@@ -56,6 +81,7 @@ function ensureSchema(db, embeddingDim) {
|
|
|
56
81
|
// Check stored version — if it differs from DB_VERSION, drop and recreate all tables
|
|
57
82
|
const storedVersion = getMeta(db, "version");
|
|
58
83
|
if (storedVersion && storedVersion !== String(DB_VERSION)) {
|
|
84
|
+
db.exec("DROP TABLE IF EXISTS embeddings");
|
|
59
85
|
db.exec("DROP TABLE IF EXISTS entries_vec");
|
|
60
86
|
db.exec("DROP TABLE IF EXISTS entries_fts");
|
|
61
87
|
db.exec("DROP INDEX IF EXISTS idx_entries_dir");
|
|
@@ -77,11 +103,17 @@ function ensureSchema(db, embeddingDim) {
|
|
|
77
103
|
|
|
78
104
|
CREATE INDEX IF NOT EXISTS idx_entries_dir ON entries(dir_path);
|
|
79
105
|
CREATE INDEX IF NOT EXISTS idx_entries_type ON entries(entry_type);
|
|
106
|
+
`);
|
|
107
|
+
// BLOB-based embedding storage (always available, no sqlite-vec needed)
|
|
108
|
+
db.exec(`
|
|
109
|
+
CREATE TABLE IF NOT EXISTS embeddings (
|
|
110
|
+
id INTEGER PRIMARY KEY,
|
|
111
|
+
embedding BLOB NOT NULL,
|
|
112
|
+
FOREIGN KEY (id) REFERENCES entries(id)
|
|
113
|
+
);
|
|
80
114
|
`);
|
|
81
115
|
// FTS5 table — standalone with explicit entry_id for joining
|
|
82
|
-
const ftsExists = db
|
|
83
|
-
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entries_fts'")
|
|
84
|
-
.get();
|
|
116
|
+
const ftsExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entries_fts'").get();
|
|
85
117
|
if (!ftsExists) {
|
|
86
118
|
db.exec(`
|
|
87
119
|
CREATE VIRTUAL TABLE entries_fts USING fts5(
|
|
@@ -92,18 +124,18 @@ function ensureSchema(db, embeddingDim) {
|
|
|
92
124
|
`);
|
|
93
125
|
}
|
|
94
126
|
// sqlite-vec table
|
|
95
|
-
if (
|
|
127
|
+
if (isVecAvailable(db)) {
|
|
96
128
|
// Check if stored embedding dimension differs from configured one
|
|
97
129
|
const storedDim = getMeta(db, "embeddingDim");
|
|
98
130
|
if (storedDim && storedDim !== String(embeddingDim)) {
|
|
99
131
|
try {
|
|
100
132
|
db.exec("DROP TABLE IF EXISTS entries_vec");
|
|
101
133
|
}
|
|
102
|
-
catch {
|
|
134
|
+
catch {
|
|
135
|
+
/* ignore */
|
|
136
|
+
}
|
|
103
137
|
}
|
|
104
|
-
const vecExists = db
|
|
105
|
-
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entries_vec'")
|
|
106
|
-
.get();
|
|
138
|
+
const vecExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entries_vec'").get();
|
|
107
139
|
if (!vecExists) {
|
|
108
140
|
db.exec(`
|
|
109
141
|
CREATE VIRTUAL TABLE entries_vec USING vec0(
|
|
@@ -147,15 +179,21 @@ export function upsertEntry(db, entryKey, dirPath, filePath, stashDir, entry, se
|
|
|
147
179
|
return row.id;
|
|
148
180
|
}
|
|
149
181
|
export function deleteEntriesByDir(db, dirPath) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
.
|
|
154
|
-
|
|
182
|
+
const ids = db.prepare("SELECT id FROM entries WHERE dir_path = ?").all(dirPath);
|
|
183
|
+
for (const { id } of ids) {
|
|
184
|
+
try {
|
|
185
|
+
db.prepare("DELETE FROM embeddings WHERE id = ?").run(id);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
/* ignore */
|
|
189
|
+
}
|
|
190
|
+
if (isVecAvailable(db)) {
|
|
155
191
|
try {
|
|
156
192
|
db.prepare("DELETE FROM entries_vec WHERE id = ?").run(id);
|
|
157
193
|
}
|
|
158
|
-
catch {
|
|
194
|
+
catch {
|
|
195
|
+
/* ignore */
|
|
196
|
+
}
|
|
159
197
|
}
|
|
160
198
|
}
|
|
161
199
|
db.prepare("DELETE FROM entries WHERE dir_path = ?").run(dirPath);
|
|
@@ -166,32 +204,67 @@ export function rebuildFts(db) {
|
|
|
166
204
|
}
|
|
167
205
|
// ── Vector operations ───────────────────────────────────────────────────────
|
|
168
206
|
export function upsertEmbedding(db, entryId, embedding) {
|
|
169
|
-
if (!vecAvailable)
|
|
170
|
-
return;
|
|
171
207
|
const buf = float32Buffer(embedding);
|
|
172
|
-
|
|
173
|
-
|
|
208
|
+
// Always write to BLOB table (works without sqlite-vec)
|
|
209
|
+
db.prepare("INSERT OR REPLACE INTO embeddings (id, embedding) VALUES (?, ?)").run(entryId, buf);
|
|
210
|
+
// Also write to sqlite-vec table when available (fast path)
|
|
211
|
+
if (isVecAvailable(db)) {
|
|
212
|
+
try {
|
|
213
|
+
db.prepare("DELETE FROM entries_vec WHERE id = ?").run(entryId);
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
/* ignore */
|
|
217
|
+
}
|
|
218
|
+
db.prepare("INSERT INTO entries_vec (id, embedding) VALUES (?, ?)").run(entryId, buf);
|
|
174
219
|
}
|
|
175
|
-
catch { /* ignore */ }
|
|
176
|
-
db.prepare("INSERT INTO entries_vec (id, embedding) VALUES (?, ?)").run(entryId, buf);
|
|
177
220
|
}
|
|
178
221
|
export function searchVec(db, queryEmbedding, k) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
222
|
+
// Fast path: use sqlite-vec when available
|
|
223
|
+
if (isVecAvailable(db)) {
|
|
224
|
+
const buf = float32Buffer(queryEmbedding);
|
|
225
|
+
try {
|
|
226
|
+
return db
|
|
227
|
+
.prepare("SELECT id, distance FROM entries_vec WHERE embedding MATCH ? AND k = ?")
|
|
228
|
+
.all(buf, k);
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
189
233
|
}
|
|
234
|
+
// Fallback: JS-based cosine similarity over BLOB table
|
|
235
|
+
return searchBlobVec(db, queryEmbedding, k);
|
|
190
236
|
}
|
|
191
237
|
function float32Buffer(vec) {
|
|
192
238
|
const f32 = new Float32Array(vec);
|
|
193
239
|
return Buffer.from(f32.buffer);
|
|
194
240
|
}
|
|
241
|
+
function bufferToFloat32(buf) {
|
|
242
|
+
const f32 = new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
|
|
243
|
+
return Array.from(f32);
|
|
244
|
+
}
|
|
245
|
+
function searchBlobVec(db, queryEmbedding, k) {
|
|
246
|
+
try {
|
|
247
|
+
const rows = db.prepare("SELECT id, embedding FROM embeddings").all();
|
|
248
|
+
if (rows.length === 0)
|
|
249
|
+
return [];
|
|
250
|
+
const scored = [];
|
|
251
|
+
for (const row of rows) {
|
|
252
|
+
const embedding = bufferToFloat32(row.embedding);
|
|
253
|
+
const similarity = cosineSimilarity(queryEmbedding, embedding);
|
|
254
|
+
scored.push({ id: row.id, similarity });
|
|
255
|
+
}
|
|
256
|
+
scored.sort((a, b) => b.similarity - a.similarity);
|
|
257
|
+
// Convert cosine similarity to L2 distance for compatibility with sqlite-vec interface
|
|
258
|
+
// For normalized vectors: L2² = 2(1 - cos_sim)
|
|
259
|
+
return scored.slice(0, k).map(({ id, similarity }) => ({
|
|
260
|
+
id,
|
|
261
|
+
distance: Math.sqrt(2 * Math.max(0, 1 - similarity)),
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
}
|
|
195
268
|
// ── FTS5 search ─────────────────────────────────────────────────────────────
|
|
196
269
|
export function searchFts(db, query, limit, entryType) {
|
|
197
270
|
const ftsQuery = sanitizeFtsQuery(query);
|
|
@@ -242,18 +315,19 @@ function sanitizeFtsQuery(query) {
|
|
|
242
315
|
const tokens = query
|
|
243
316
|
.replace(/[^a-zA-Z0-9\s]/g, " ")
|
|
244
317
|
.split(/\s+/)
|
|
245
|
-
.filter((t) => t.length
|
|
318
|
+
.filter((t) => t.length >= 1);
|
|
246
319
|
if (tokens.length === 0)
|
|
247
320
|
return "";
|
|
248
321
|
// Use unquoted tokens so the porter stemmer can normalize word forms
|
|
249
|
-
return tokens.join("
|
|
322
|
+
return tokens.join(" ");
|
|
250
323
|
}
|
|
251
324
|
// ── All entries ─────────────────────────────────────────────────────────────
|
|
252
325
|
export function getAllEntries(db, entryType) {
|
|
253
326
|
let sql;
|
|
254
327
|
let params;
|
|
255
328
|
if (entryType && entryType !== "any") {
|
|
256
|
-
sql =
|
|
329
|
+
sql =
|
|
330
|
+
"SELECT id, entry_key, dir_path, file_path, stash_dir, entry_json, search_text FROM entries WHERE entry_type = ?";
|
|
257
331
|
params = [entryType];
|
|
258
332
|
}
|
|
259
333
|
else {
|
|
@@ -276,9 +350,7 @@ export function getEntryCount(db) {
|
|
|
276
350
|
return row.cnt;
|
|
277
351
|
}
|
|
278
352
|
export function getEntryById(db, id) {
|
|
279
|
-
const row = db
|
|
280
|
-
.prepare("SELECT file_path, entry_json FROM entries WHERE id = ?")
|
|
281
|
-
.get(id);
|
|
353
|
+
const row = db.prepare("SELECT file_path, entry_json FROM entries WHERE id = ?").get(id);
|
|
282
354
|
if (!row)
|
|
283
355
|
return undefined;
|
|
284
356
|
return { filePath: row.file_path, entry: JSON.parse(row.entry_json) };
|
|
@@ -60,16 +60,71 @@ export async function embed(text, embeddingConfig) {
|
|
|
60
60
|
}
|
|
61
61
|
return embedLocal(text);
|
|
62
62
|
}
|
|
63
|
+
// ── Batch embedding ─────────────────────────────────────────────────────────
|
|
64
|
+
/**
|
|
65
|
+
* Generate embeddings for multiple texts in batch.
|
|
66
|
+
* Uses the OpenAI-compatible batch API for remote endpoints (batches of 100).
|
|
67
|
+
* Falls back to sequential embedding for local transformer pipeline.
|
|
68
|
+
*/
|
|
69
|
+
export async function embedBatch(texts, embeddingConfig) {
|
|
70
|
+
if (texts.length === 0)
|
|
71
|
+
return [];
|
|
72
|
+
if (embeddingConfig) {
|
|
73
|
+
return embedRemoteBatch(texts, embeddingConfig);
|
|
74
|
+
}
|
|
75
|
+
// Local transformer: process sequentially (pipeline handles one at a time)
|
|
76
|
+
const results = [];
|
|
77
|
+
for (const text of texts) {
|
|
78
|
+
results.push(await embedLocal(text));
|
|
79
|
+
}
|
|
80
|
+
return results;
|
|
81
|
+
}
|
|
82
|
+
async function embedRemoteBatch(texts, config) {
|
|
83
|
+
const BATCH_SIZE = 100;
|
|
84
|
+
const results = [];
|
|
85
|
+
const headers = { "Content-Type": "application/json" };
|
|
86
|
+
if (config.apiKey) {
|
|
87
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
88
|
+
}
|
|
89
|
+
for (let i = 0; i < texts.length; i += BATCH_SIZE) {
|
|
90
|
+
const batch = texts.slice(i, i + BATCH_SIZE);
|
|
91
|
+
const body = {
|
|
92
|
+
input: batch,
|
|
93
|
+
model: config.model,
|
|
94
|
+
};
|
|
95
|
+
if (config.dimension) {
|
|
96
|
+
body.dimensions = config.dimension;
|
|
97
|
+
}
|
|
98
|
+
const response = await fetchWithTimeout(config.endpoint, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers,
|
|
101
|
+
body: JSON.stringify(body),
|
|
102
|
+
});
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
const respBody = await response.text().catch(() => "");
|
|
105
|
+
throw new Error(`Embedding batch request failed (${response.status}): ${respBody}`);
|
|
106
|
+
}
|
|
107
|
+
const json = (await response.json());
|
|
108
|
+
if (!json.data || json.data.length !== batch.length) {
|
|
109
|
+
throw new Error(`Unexpected embedding batch response: expected ${batch.length} embeddings, got ${json.data?.length ?? 0}`);
|
|
110
|
+
}
|
|
111
|
+
results.push(...json.data.map((d) => d.embedding));
|
|
112
|
+
}
|
|
113
|
+
return results;
|
|
114
|
+
}
|
|
63
115
|
// ── Similarity ──────────────────────────────────────────────────────────────
|
|
64
116
|
export function cosineSimilarity(a, b) {
|
|
65
117
|
const len = Math.min(a.length, b.length);
|
|
66
118
|
if (len === 0)
|
|
67
119
|
return 0;
|
|
68
|
-
let dot = 0;
|
|
120
|
+
let dot = 0, magA = 0, magB = 0;
|
|
69
121
|
for (let i = 0; i < len; i++) {
|
|
70
122
|
dot += a[i] * b[i];
|
|
123
|
+
magA += a[i] * a[i];
|
|
124
|
+
magB += b[i] * b[i];
|
|
71
125
|
}
|
|
72
|
-
|
|
126
|
+
const denom = Math.sqrt(magA) * Math.sqrt(magB);
|
|
127
|
+
return denom === 0 ? 0 : dot / denom;
|
|
73
128
|
}
|
|
74
129
|
// ── Availability check ──────────────────────────────────────────────────────
|
|
75
130
|
export async function isEmbeddingAvailable(embeddingConfig) {
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed error classes for structured exit code classification.
|
|
3
|
+
*
|
|
4
|
+
* - ConfigError -> exit 78 (configuration / environment problems)
|
|
5
|
+
* - UsageError -> exit 2 (bad CLI arguments or invalid input)
|
|
6
|
+
* - NotFoundError -> exit 1 (requested resource missing)
|
|
7
|
+
*/
|
|
8
|
+
/** Raised when configuration or environment is invalid or missing. */
|
|
9
|
+
export class ConfigError extends Error {
|
|
10
|
+
constructor(msg) {
|
|
11
|
+
super(msg);
|
|
12
|
+
this.name = "ConfigError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Raised when the user supplies invalid arguments or input. */
|
|
16
|
+
export class UsageError extends Error {
|
|
17
|
+
constructor(msg) {
|
|
18
|
+
super(msg);
|
|
19
|
+
this.name = "UsageError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Raised when a requested resource (asset, entry, file) is not found. */
|
|
23
|
+
export class NotFoundError extends Error {
|
|
24
|
+
constructor(msg) {
|
|
25
|
+
super(msg);
|
|
26
|
+
this.name = "NotFoundError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flexible asset resolution system.
|
|
3
|
+
*
|
|
4
|
+
* Provides a rich FileContext built once per file during walking, plus a
|
|
5
|
+
* matcher/renderer registry that decouples asset classification from rendering.
|
|
6
|
+
*/
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { toPosix } from "./common";
|
|
10
|
+
import { parseFrontmatter } from "./frontmatter";
|
|
11
|
+
/**
|
|
12
|
+
* Build a FileContext from a stash root and an absolute file path.
|
|
13
|
+
*
|
|
14
|
+
* Path-derived fields are computed eagerly. The content, frontmatter, and
|
|
15
|
+
* stat getters use lazy caching so the file is only read from disk when
|
|
16
|
+
* (and if) a matcher or renderer actually needs it.
|
|
17
|
+
*/
|
|
18
|
+
export function buildFileContext(stashRoot, absPath) {
|
|
19
|
+
const relPath = toPosix(path.relative(stashRoot, absPath));
|
|
20
|
+
const ext = path.extname(absPath).toLowerCase();
|
|
21
|
+
const fileName = path.basename(absPath);
|
|
22
|
+
const parentDirAbs = path.dirname(absPath);
|
|
23
|
+
const parentDir = path.basename(parentDirAbs);
|
|
24
|
+
// Compute ancestor directory segments from the POSIX relPath's directory portion.
|
|
25
|
+
// For "tools/azure/deploy/run.sh" the dir portion is "tools/azure/deploy"
|
|
26
|
+
// which splits into ["tools", "azure", "deploy"].
|
|
27
|
+
const relDir = toPosix(path.dirname(relPath));
|
|
28
|
+
const ancestorDirs = relDir === "." ? [] : relDir.split("/").filter((seg) => seg.length > 0);
|
|
29
|
+
// Lazy caches
|
|
30
|
+
let cachedContent;
|
|
31
|
+
let cachedFrontmatter;
|
|
32
|
+
let frontmatterComputed = false;
|
|
33
|
+
let cachedStat;
|
|
34
|
+
return {
|
|
35
|
+
absPath,
|
|
36
|
+
relPath,
|
|
37
|
+
ext,
|
|
38
|
+
fileName,
|
|
39
|
+
parentDir,
|
|
40
|
+
parentDirAbs,
|
|
41
|
+
ancestorDirs,
|
|
42
|
+
stashRoot,
|
|
43
|
+
content() {
|
|
44
|
+
if (cachedContent === undefined) {
|
|
45
|
+
cachedContent = fs.readFileSync(absPath, "utf8");
|
|
46
|
+
}
|
|
47
|
+
return cachedContent;
|
|
48
|
+
},
|
|
49
|
+
frontmatter() {
|
|
50
|
+
if (!frontmatterComputed) {
|
|
51
|
+
const raw = this.content();
|
|
52
|
+
const parsed = parseFrontmatter(raw);
|
|
53
|
+
cachedFrontmatter = Object.keys(parsed.data).length > 0 ? parsed.data : null;
|
|
54
|
+
frontmatterComputed = true;
|
|
55
|
+
}
|
|
56
|
+
return cachedFrontmatter;
|
|
57
|
+
},
|
|
58
|
+
stat() {
|
|
59
|
+
if (cachedStat === undefined) {
|
|
60
|
+
cachedStat = fs.statSync(absPath);
|
|
61
|
+
}
|
|
62
|
+
return cachedStat;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// ── Registry ─────────────────────────────────────────────────────────────────
|
|
67
|
+
/** Ordered list of registered matchers. Later registrations win ties. */
|
|
68
|
+
const matchers = [];
|
|
69
|
+
/** Renderer lookup by name. */
|
|
70
|
+
const renderers = new Map();
|
|
71
|
+
let builtinsInitialized = false;
|
|
72
|
+
/** Pluggable initializer set via `setBuiltinRegistrar`. */
|
|
73
|
+
let builtinRegistrar = null;
|
|
74
|
+
/**
|
|
75
|
+
* Set the function that registers built-in matchers and renderers.
|
|
76
|
+
*
|
|
77
|
+
* This breaks the static import cycle: `file-context.ts` does not need to
|
|
78
|
+
* import `matchers.ts` or `renderers.ts` at the top level. Instead, a
|
|
79
|
+
* one-time call from outside (e.g. the test file or CLI entry) provides the
|
|
80
|
+
* registration callback.
|
|
81
|
+
*
|
|
82
|
+
* If no registrar is set by the time `ensureBuiltinsRegistered` runs, it
|
|
83
|
+
* falls back to a dynamic `require()` for backward compatibility.
|
|
84
|
+
*/
|
|
85
|
+
export function setBuiltinRegistrar(fn) {
|
|
86
|
+
builtinRegistrar = fn;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Ensure that built-in matchers and renderers are registered.
|
|
90
|
+
* Called lazily on first use of runMatchers/getRenderer.
|
|
91
|
+
*
|
|
92
|
+
* Uses the registrar set via `setBuiltinRegistrar`, or falls back to
|
|
93
|
+
* a direct import of the registration modules. The dynamic import
|
|
94
|
+
* avoids a static circular dependency between file-context, renderers,
|
|
95
|
+
* and asset-spec.
|
|
96
|
+
*/
|
|
97
|
+
function ensureBuiltinsRegistered() {
|
|
98
|
+
if (builtinsInitialized)
|
|
99
|
+
return;
|
|
100
|
+
builtinsInitialized = true;
|
|
101
|
+
if (builtinRegistrar) {
|
|
102
|
+
builtinRegistrar();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// Lazy inline require avoids a top-level static import cycle:
|
|
106
|
+
// file-context -> renderers -> asset-spec -> asset-type-handler -> handlers -> file-context
|
|
107
|
+
// These are only evaluated once and only when no explicit registrar was set.
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
109
|
+
const { registerBuiltinMatchers } = require("./matchers");
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
111
|
+
const { registerBuiltinRenderers } = require("./renderers");
|
|
112
|
+
registerBuiltinMatchers();
|
|
113
|
+
registerBuiltinRenderers();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Register an AssetMatcher.
|
|
117
|
+
*
|
|
118
|
+
* Matchers are evaluated in registration order. When two matchers produce
|
|
119
|
+
* the same specificity score, the one registered later wins.
|
|
120
|
+
*/
|
|
121
|
+
export function registerMatcher(matcher) {
|
|
122
|
+
matchers.push(matcher);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Register an AssetRenderer.
|
|
126
|
+
*
|
|
127
|
+
* If a renderer with the same name already exists it is silently replaced.
|
|
128
|
+
*/
|
|
129
|
+
export function registerRenderer(renderer) {
|
|
130
|
+
renderers.set(renderer.name, renderer);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Look up a renderer by name.
|
|
134
|
+
*/
|
|
135
|
+
export function getRenderer(name) {
|
|
136
|
+
ensureBuiltinsRegistered();
|
|
137
|
+
return renderers.get(name);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Return all registered renderers (snapshot, safe to iterate).
|
|
141
|
+
*/
|
|
142
|
+
export function getAllRenderers() {
|
|
143
|
+
ensureBuiltinsRegistered();
|
|
144
|
+
return Array.from(renderers.values());
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Run every registered matcher against a FileContext and return the
|
|
148
|
+
* highest-specificity result.
|
|
149
|
+
*
|
|
150
|
+
* Resolution rules:
|
|
151
|
+
* 1. Every matcher is invoked; null returns are discarded.
|
|
152
|
+
* 2. Results are ranked by specificity (descending).
|
|
153
|
+
* 3. Ties are broken by registration order: the matcher registered later wins
|
|
154
|
+
* (this lets user-registered matchers override built-in ones).
|
|
155
|
+
* 4. Returns null when no matcher claims the file.
|
|
156
|
+
*/
|
|
157
|
+
export function runMatchers(ctx) {
|
|
158
|
+
ensureBuiltinsRegistered();
|
|
159
|
+
// Collect (result, registrationIndex) pairs from all matchers.
|
|
160
|
+
const hits = [];
|
|
161
|
+
for (let i = 0; i < matchers.length; i++) {
|
|
162
|
+
const result = matchers[i](ctx);
|
|
163
|
+
if (result !== null) {
|
|
164
|
+
hits.push({ result, index: i });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (hits.length === 0)
|
|
168
|
+
return null;
|
|
169
|
+
// Sort by specificity descending, then by registration index descending (later wins ties).
|
|
170
|
+
hits.sort((a, b) => {
|
|
171
|
+
const specDiff = b.result.specificity - a.result.specificity;
|
|
172
|
+
if (specDiff !== 0)
|
|
173
|
+
return specDiff;
|
|
174
|
+
return b.index - a.index;
|
|
175
|
+
});
|
|
176
|
+
return hits[0].result;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Build a RenderContext by merging a FileContext with its winning MatchResult
|
|
180
|
+
* and the list of stash search paths.
|
|
181
|
+
*/
|
|
182
|
+
export function buildRenderContext(ctx, match, stashDirs) {
|
|
183
|
+
return {
|
|
184
|
+
...ctx,
|
|
185
|
+
matchResult: match,
|
|
186
|
+
stashDirs,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -18,7 +18,7 @@ export function parseFrontmatter(raw) {
|
|
|
18
18
|
let currentKey = null;
|
|
19
19
|
let nested = null;
|
|
20
20
|
for (const line of parsedBlock.frontmatter.split(/\r?\n/)) {
|
|
21
|
-
const indented = line.match(/^
|
|
21
|
+
const indented = line.match(/^ {2}(\w[\w-]*):\s*(.+)$/);
|
|
22
22
|
if (indented && currentKey && nested) {
|
|
23
23
|
nested[indented[1]] = parseYamlScalar(indented[2].trim());
|
|
24
24
|
continue;
|
|
@@ -10,9 +10,7 @@ export function githubHeaders() {
|
|
|
10
10
|
return headers;
|
|
11
11
|
}
|
|
12
12
|
export function asRecord(value) {
|
|
13
|
-
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
14
|
-
? value
|
|
15
|
-
: {};
|
|
13
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
16
14
|
}
|
|
17
15
|
export function asString(value) {
|
|
18
16
|
return typeof value === "string" && value ? value : undefined;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getRenderer } from "../file-context";
|
|
2
|
+
import { showInputToRenderContext } from "./handler-bridge";
|
|
3
|
+
import { isMarkdownFile, markdownAssetPath, markdownCanonicalName } from "./markdown-helpers";
|
|
4
|
+
export const agentHandler = {
|
|
5
|
+
typeName: "agent",
|
|
6
|
+
stashDir: "agents",
|
|
7
|
+
isRelevantFile: isMarkdownFile,
|
|
8
|
+
toCanonicalName: markdownCanonicalName,
|
|
9
|
+
toAssetPath: markdownAssetPath,
|
|
10
|
+
buildShowResponse(input) {
|
|
11
|
+
const renderer = getRenderer("agent-md");
|
|
12
|
+
const ctx = showInputToRenderContext(input, "agent-md");
|
|
13
|
+
return renderer.buildShowResponse(ctx);
|
|
14
|
+
},
|
|
15
|
+
defaultUsageGuide: [
|
|
16
|
+
"Read the .md file and dispatch an agent using the content of the file. Use modelHint/toolPolicy when present to run the agent with compatible settings.",
|
|
17
|
+
"Use with `akm show <openRef>` to get the full prompt payload.",
|
|
18
|
+
],
|
|
19
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getRenderer } from "../file-context";
|
|
2
|
+
import { showInputToRenderContext } from "./handler-bridge";
|
|
3
|
+
import { isMarkdownFile, markdownAssetPath, markdownCanonicalName } from "./markdown-helpers";
|
|
4
|
+
export const commandHandler = {
|
|
5
|
+
typeName: "command",
|
|
6
|
+
stashDir: "commands",
|
|
7
|
+
isRelevantFile: isMarkdownFile,
|
|
8
|
+
toCanonicalName: markdownCanonicalName,
|
|
9
|
+
toAssetPath: markdownAssetPath,
|
|
10
|
+
buildShowResponse(input) {
|
|
11
|
+
const renderer = getRenderer("command-md");
|
|
12
|
+
const ctx = showInputToRenderContext(input, "command-md");
|
|
13
|
+
return renderer.buildShowResponse(ctx);
|
|
14
|
+
},
|
|
15
|
+
defaultUsageGuide: [
|
|
16
|
+
"Read the .md file, fill $ARGUMENTS placeholders, and run it in the current repo context.",
|
|
17
|
+
"Use `akm show <openRef>` to retrieve the command template body.",
|
|
18
|
+
"When `agent` is specified, dispatch the command to that agent.",
|
|
19
|
+
],
|
|
20
|
+
};
|