@agentmemory/agentmemory 0.9.18 → 0.9.20
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/AGENTS.md +2 -2
- package/README.md +4 -4
- package/dist/cli.mjs +12 -8
- package/dist/cli.mjs.map +1 -1
- package/dist/hooks/post-commit.d.mts +1 -0
- package/dist/hooks/post-commit.mjs +102 -0
- package/dist/hooks/post-commit.mjs.map +1 -0
- package/dist/index.mjs +355 -66
- package/dist/index.mjs.map +1 -1
- package/dist/{src-C7vygXCj.mjs → src-DPSaLB5-.mjs} +321 -66
- package/dist/src-DPSaLB5-.mjs.map +1 -0
- package/dist/{standalone-kg2TedgD.mjs → standalone-DMLk7YxP.mjs} +2 -2
- package/dist/{standalone-kg2TedgD.mjs.map → standalone-DMLk7YxP.mjs.map} +1 -1
- package/dist/standalone.mjs +34 -1
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-BFKFKmYh.mjs → tools-registry-Dz8ssuMf.mjs} +34 -1
- package/dist/tools-registry-Dz8ssuMf.mjs.map +1 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +5 -1
- package/plugin/scripts/post-commit.d.mts +1 -0
- package/plugin/scripts/post-commit.mjs +102 -0
- package/plugin/scripts/post-commit.mjs.map +1 -0
- package/plugin/skills/commit-context/SKILL.md +19 -0
- package/plugin/skills/commit-history/SKILL.md +20 -0
- package/plugin/skills/handoff/SKILL.md +21 -0
- package/plugin/skills/recap/SKILL.md +25 -0
- package/dist/src-C7vygXCj.mjs.map +0 -1
- package/dist/tools-registry-BFKFKmYh.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
|
|
5
|
+
//#region src/hooks/post-commit.ts
|
|
6
|
+
const exec = promisify(execFile);
|
|
7
|
+
function isSdkChildContext(payload) {
|
|
8
|
+
if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true;
|
|
9
|
+
if (!payload || typeof payload !== "object") return false;
|
|
10
|
+
return payload.entrypoint === "sdk-ts";
|
|
11
|
+
}
|
|
12
|
+
const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111";
|
|
13
|
+
const SECRET = process.env["AGENTMEMORY_SECRET"] || "";
|
|
14
|
+
const TIMEOUT_MS = 1500;
|
|
15
|
+
function authHeaders() {
|
|
16
|
+
const h = { "Content-Type": "application/json" };
|
|
17
|
+
if (SECRET) h["Authorization"] = `Bearer ${SECRET}`;
|
|
18
|
+
return h;
|
|
19
|
+
}
|
|
20
|
+
async function git(args, cwd) {
|
|
21
|
+
try {
|
|
22
|
+
const { stdout } = await exec("git", args, {
|
|
23
|
+
cwd,
|
|
24
|
+
timeout: 1500
|
|
25
|
+
});
|
|
26
|
+
return stdout.trim();
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function main() {
|
|
32
|
+
let input = "";
|
|
33
|
+
for await (const chunk of process.stdin) input += chunk;
|
|
34
|
+
let data = {};
|
|
35
|
+
if (input.trim()) try {
|
|
36
|
+
data = JSON.parse(input);
|
|
37
|
+
} catch {}
|
|
38
|
+
if (isSdkChildContext(data)) return;
|
|
39
|
+
const cwd = data.cwd || process.env["AGENTMEMORY_CWD"] || process.cwd();
|
|
40
|
+
const sessionId = data.session_id || process.env["AGENTMEMORY_SESSION_ID"] || void 0;
|
|
41
|
+
const sha = process.env["AGENTMEMORY_COMMIT_SHA"] || await git(["rev-parse", "HEAD"], cwd);
|
|
42
|
+
if (!sha) return;
|
|
43
|
+
const branch = await git([
|
|
44
|
+
"rev-parse",
|
|
45
|
+
"--abbrev-ref",
|
|
46
|
+
"HEAD"
|
|
47
|
+
], cwd);
|
|
48
|
+
const repo = await git([
|
|
49
|
+
"config",
|
|
50
|
+
"--get",
|
|
51
|
+
"remote.origin.url"
|
|
52
|
+
], cwd);
|
|
53
|
+
const message = await git([
|
|
54
|
+
"log",
|
|
55
|
+
"-1",
|
|
56
|
+
"--pretty=%B",
|
|
57
|
+
sha
|
|
58
|
+
], cwd);
|
|
59
|
+
const author = await git([
|
|
60
|
+
"log",
|
|
61
|
+
"-1",
|
|
62
|
+
"--pretty=%an <%ae>",
|
|
63
|
+
sha
|
|
64
|
+
], cwd);
|
|
65
|
+
const authoredAt = await git([
|
|
66
|
+
"log",
|
|
67
|
+
"-1",
|
|
68
|
+
"--pretty=%aI",
|
|
69
|
+
sha
|
|
70
|
+
], cwd);
|
|
71
|
+
const filesRaw = await git([
|
|
72
|
+
"diff-tree",
|
|
73
|
+
"--no-commit-id",
|
|
74
|
+
"--name-only",
|
|
75
|
+
"-r",
|
|
76
|
+
sha
|
|
77
|
+
], cwd);
|
|
78
|
+
const files = filesRaw ? filesRaw.split("\n").filter(Boolean) : void 0;
|
|
79
|
+
const body = {
|
|
80
|
+
sessionId,
|
|
81
|
+
sha,
|
|
82
|
+
branch: branch || void 0,
|
|
83
|
+
repo: repo || void 0,
|
|
84
|
+
message: message || void 0,
|
|
85
|
+
author: author || void 0,
|
|
86
|
+
authoredAt: authoredAt || void 0,
|
|
87
|
+
files
|
|
88
|
+
};
|
|
89
|
+
try {
|
|
90
|
+
await fetch(`${REST_URL}/agentmemory/session/commit`, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: authHeaders(),
|
|
93
|
+
body: JSON.stringify(body),
|
|
94
|
+
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
95
|
+
});
|
|
96
|
+
} catch {}
|
|
97
|
+
}
|
|
98
|
+
main();
|
|
99
|
+
|
|
100
|
+
//#endregion
|
|
101
|
+
export { };
|
|
102
|
+
//# sourceMappingURL=post-commit.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-commit.mjs","names":[],"sources":["../../src/hooks/post-commit.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst exec = promisify(execFile);\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = process.env[\"AGENTMEMORY_SECRET\"] || \"\";\nconst TIMEOUT_MS = 1500;\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function git(args: string[], cwd: string): Promise<string | null> {\n try {\n const { stdout } = await exec(\"git\", args, { cwd, timeout: 1500 });\n return stdout.trim();\n } catch {\n return null;\n }\n}\n\nasync function main() {\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown> = {};\n if (input.trim()) {\n try {\n data = JSON.parse(input);\n } catch {\n // Direct invocation from .git/hooks/post-commit may pass no stdin.\n }\n }\n\n if (isSdkChildContext(data)) return;\n\n const cwd =\n (data.cwd as string) ||\n process.env[\"AGENTMEMORY_CWD\"] ||\n process.cwd();\n const sessionId =\n (data.session_id as string) ||\n process.env[\"AGENTMEMORY_SESSION_ID\"] ||\n undefined;\n\n const sha =\n process.env[\"AGENTMEMORY_COMMIT_SHA\"] ||\n (await git([\"rev-parse\", \"HEAD\"], cwd));\n if (!sha) return;\n\n const branch = await git([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], cwd);\n const repo = await git([\"config\", \"--get\", \"remote.origin.url\"], cwd);\n const message = await git([\"log\", \"-1\", \"--pretty=%B\", sha], cwd);\n const author = await git([\"log\", \"-1\", \"--pretty=%an <%ae>\", sha], cwd);\n const authoredAt = await git([\"log\", \"-1\", \"--pretty=%aI\", sha], cwd);\n const filesRaw = await git(\n [\"diff-tree\", \"--no-commit-id\", \"--name-only\", \"-r\", sha],\n cwd,\n );\n const files = filesRaw ? filesRaw.split(\"\\n\").filter(Boolean) : undefined;\n\n const body = {\n sessionId,\n sha,\n branch: branch || undefined,\n repo: repo || undefined,\n message: message || undefined,\n author: author || undefined,\n authoredAt: authoredAt || undefined,\n files,\n };\n\n try {\n await fetch(`${REST_URL}/agentmemory/session/commit`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(TIMEOUT_MS),\n });\n } catch {\n // best-effort\n }\n}\n\nmain();\n"],"mappings":";;;;;AAKA,MAAM,OAAO,UAAU,SAAS;AAEhC,SAAS,kBAAkB,SAA2B;AACpD,KAAI,QAAQ,IAAI,6BAA6B,IAAK,QAAO;AACzD,KAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAQ,QAAqC,eAAe;;AAG9D,MAAM,WAAW,QAAQ,IAAI,sBAAsB;AACnD,MAAM,SAAS,QAAQ,IAAI,yBAAyB;AACpD,MAAM,aAAa;AAEnB,SAAS,cAAsC;CAC7C,MAAM,IAA4B,EAAE,gBAAgB,oBAAoB;AACxE,KAAI,OAAQ,GAAE,mBAAmB,UAAU;AAC3C,QAAO;;AAGT,eAAe,IAAI,MAAgB,KAAqC;AACtE,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,KAAK,OAAO,MAAM;GAAE;GAAK,SAAS;GAAM,CAAC;AAClE,SAAO,OAAO,MAAM;SACd;AACN,SAAO;;;AAIX,eAAe,OAAO;CACpB,IAAI,QAAQ;AACZ,YAAW,MAAM,SAAS,QAAQ,MAChC,UAAS;CAGX,IAAI,OAAgC,EAAE;AACtC,KAAI,MAAM,MAAM,CACd,KAAI;AACF,SAAO,KAAK,MAAM,MAAM;SAClB;AAKV,KAAI,kBAAkB,KAAK,CAAE;CAE7B,MAAM,MACH,KAAK,OACN,QAAQ,IAAI,sBACZ,QAAQ,KAAK;CACf,MAAM,YACH,KAAK,cACN,QAAQ,IAAI,6BACZ;CAEF,MAAM,MACJ,QAAQ,IAAI,6BACX,MAAM,IAAI,CAAC,aAAa,OAAO,EAAE,IAAI;AACxC,KAAI,CAAC,IAAK;CAEV,MAAM,SAAS,MAAM,IAAI;EAAC;EAAa;EAAgB;EAAO,EAAE,IAAI;CACpE,MAAM,OAAO,MAAM,IAAI;EAAC;EAAU;EAAS;EAAoB,EAAE,IAAI;CACrE,MAAM,UAAU,MAAM,IAAI;EAAC;EAAO;EAAM;EAAe;EAAI,EAAE,IAAI;CACjE,MAAM,SAAS,MAAM,IAAI;EAAC;EAAO;EAAM;EAAsB;EAAI,EAAE,IAAI;CACvE,MAAM,aAAa,MAAM,IAAI;EAAC;EAAO;EAAM;EAAgB;EAAI,EAAE,IAAI;CACrE,MAAM,WAAW,MAAM,IACrB;EAAC;EAAa;EAAkB;EAAe;EAAM;EAAI,EACzD,IACD;CACD,MAAM,QAAQ,WAAW,SAAS,MAAM,KAAK,CAAC,OAAO,QAAQ,GAAG;CAEhE,MAAM,OAAO;EACX;EACA;EACA,QAAQ,UAAU;EAClB,MAAM,QAAQ;EACd,SAAS,WAAW;EACpB,QAAQ,UAAU;EAClB,YAAY,cAAc;EAC1B;EACD;AAED,KAAI;AACF,QAAM,MAAM,GAAG,SAAS,8BAA8B;GACpD,QAAQ;GACR,SAAS,aAAa;GACtB,MAAM,KAAK,UAAU,KAAK;GAC1B,QAAQ,YAAY,QAAQ,WAAW;GACxC,CAAC;SACI;;AAKV,MAAM"}
|
package/dist/index.mjs
CHANGED
|
@@ -418,11 +418,63 @@ var NoopProvider = class {
|
|
|
418
418
|
}
|
|
419
419
|
};
|
|
420
420
|
|
|
421
|
+
//#endregion
|
|
422
|
+
//#region src/providers/_openai-shared.ts
|
|
423
|
+
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
|
|
424
|
+
const DEFAULT_AZURE_API_VERSION = "2024-08-01-preview";
|
|
425
|
+
function detectAzure(baseUrl) {
|
|
426
|
+
try {
|
|
427
|
+
return new URL(baseUrl).hostname.endsWith(".openai.azure.com");
|
|
428
|
+
} catch {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
function azureStyleOf(baseUrl) {
|
|
433
|
+
try {
|
|
434
|
+
const u = new URL(baseUrl);
|
|
435
|
+
if (/\/openai\/deployments\//.test(u.pathname)) return "legacy";
|
|
436
|
+
return "v1";
|
|
437
|
+
} catch {
|
|
438
|
+
return "v1";
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function legacyAzureUrl(baseUrl, path, apiVersion) {
|
|
442
|
+
const url = new URL(baseUrl);
|
|
443
|
+
url.pathname = `${url.pathname.replace(/\/+$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
444
|
+
url.searchParams.set("api-version", apiVersion);
|
|
445
|
+
return url.toString();
|
|
446
|
+
}
|
|
447
|
+
function v1AzureUrl(baseUrl, path) {
|
|
448
|
+
const url = new URL(baseUrl);
|
|
449
|
+
const route = path.startsWith("/") ? path.slice(1) : path;
|
|
450
|
+
url.pathname = `${url.pathname.replace(/\/?openai(?:\/v1)?\/?$/, "").replace(/\/+$/, "")}/openai/v1/${route}`;
|
|
451
|
+
return url.toString();
|
|
452
|
+
}
|
|
453
|
+
function buildChatUrl(baseUrl, isAzure, azureApiVersion) {
|
|
454
|
+
if (isAzure) return azureStyleOf(baseUrl) === "legacy" ? legacyAzureUrl(baseUrl, "/chat/completions", azureApiVersion) : v1AzureUrl(baseUrl, "/chat/completions");
|
|
455
|
+
return `${baseUrl}/v1/chat/completions`;
|
|
456
|
+
}
|
|
457
|
+
function buildEmbeddingUrl(baseUrl, isAzure, azureApiVersion) {
|
|
458
|
+
if (isAzure) return azureStyleOf(baseUrl) === "legacy" ? legacyAzureUrl(baseUrl, "/embeddings", azureApiVersion) : v1AzureUrl(baseUrl, "/embeddings");
|
|
459
|
+
return `${baseUrl}/v1/embeddings`;
|
|
460
|
+
}
|
|
461
|
+
function buildAuthHeaders(apiKey, isAzure) {
|
|
462
|
+
if (isAzure) return {
|
|
463
|
+
"Content-Type": "application/json",
|
|
464
|
+
"api-key": apiKey
|
|
465
|
+
};
|
|
466
|
+
return {
|
|
467
|
+
"Content-Type": "application/json",
|
|
468
|
+
Authorization: `Bearer ${apiKey}`
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
function normalizeBaseUrl(raw) {
|
|
472
|
+
return (raw || DEFAULT_OPENAI_BASE_URL).replace(/\/+$/, "");
|
|
473
|
+
}
|
|
474
|
+
|
|
421
475
|
//#endregion
|
|
422
476
|
//#region src/providers/openai.ts
|
|
423
|
-
const DEFAULT_BASE_URL$1 = "https://api.openai.com";
|
|
424
477
|
const DEFAULT_TIMEOUT_MS = 6e4;
|
|
425
|
-
const DEFAULT_AZURE_API_VERSION = "2024-08-01-preview";
|
|
426
478
|
/**
|
|
427
479
|
* OpenAI-compatible LLM provider.
|
|
428
480
|
*
|
|
@@ -469,7 +521,7 @@ var OpenAIProvider = class {
|
|
|
469
521
|
this.apiKey = apiKey;
|
|
470
522
|
this.model = model;
|
|
471
523
|
this.maxTokens = maxTokens;
|
|
472
|
-
this.baseUrl = (baseURL || getEnvVar("OPENAI_BASE_URL")
|
|
524
|
+
this.baseUrl = normalizeBaseUrl(baseURL || getEnvVar("OPENAI_BASE_URL"));
|
|
473
525
|
this.reasoningEffort = getEnvVar("OPENAI_REASONING_EFFORT") || void 0;
|
|
474
526
|
this.timeoutMs = resolveTimeout();
|
|
475
527
|
this.azureApiVersion = getEnvVar("OPENAI_API_VERSION") || DEFAULT_AZURE_API_VERSION;
|
|
@@ -481,25 +533,8 @@ var OpenAIProvider = class {
|
|
|
481
533
|
async summarize(systemPrompt, userPrompt) {
|
|
482
534
|
return this.call(systemPrompt, userPrompt);
|
|
483
535
|
}
|
|
484
|
-
buildUrl() {
|
|
485
|
-
if (this.isAzure) {
|
|
486
|
-
const sep = this.baseUrl.includes("?") ? "&" : "?";
|
|
487
|
-
return `${this.baseUrl}/chat/completions${sep}api-version=${encodeURIComponent(this.azureApiVersion)}`;
|
|
488
|
-
}
|
|
489
|
-
return `${this.baseUrl}/v1/chat/completions`;
|
|
490
|
-
}
|
|
491
|
-
buildHeaders() {
|
|
492
|
-
if (this.isAzure) return {
|
|
493
|
-
"Content-Type": "application/json",
|
|
494
|
-
"api-key": this.apiKey
|
|
495
|
-
};
|
|
496
|
-
return {
|
|
497
|
-
"Content-Type": "application/json",
|
|
498
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
536
|
async call(systemPrompt, userPrompt) {
|
|
502
|
-
const url = this.
|
|
537
|
+
const url = buildChatUrl(this.baseUrl, this.isAzure, this.azureApiVersion);
|
|
503
538
|
const body = {
|
|
504
539
|
model: this.model,
|
|
505
540
|
max_tokens: this.maxTokens,
|
|
@@ -516,7 +551,7 @@ var OpenAIProvider = class {
|
|
|
516
551
|
try {
|
|
517
552
|
response = await fetchWithTimeout(url, {
|
|
518
553
|
method: "POST",
|
|
519
|
-
headers: this.
|
|
554
|
+
headers: buildAuthHeaders(this.apiKey, this.isAzure),
|
|
520
555
|
body: JSON.stringify(body)
|
|
521
556
|
}, this.timeoutMs);
|
|
522
557
|
} catch (err) {
|
|
@@ -550,13 +585,6 @@ function parsePositiveInt(raw) {
|
|
|
550
585
|
const n = Number(trimmed);
|
|
551
586
|
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
552
587
|
}
|
|
553
|
-
function detectAzure(baseUrl) {
|
|
554
|
-
try {
|
|
555
|
-
return new URL(baseUrl).hostname.endsWith(".openai.azure.com");
|
|
556
|
-
} catch {
|
|
557
|
-
return false;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
588
|
|
|
561
589
|
//#endregion
|
|
562
590
|
//#region src/providers/openrouter.ts
|
|
@@ -786,7 +814,6 @@ function l2Normalize(vec) {
|
|
|
786
814
|
|
|
787
815
|
//#endregion
|
|
788
816
|
//#region src/providers/embedding/openai.ts
|
|
789
|
-
const DEFAULT_BASE_URL = "https://api.openai.com";
|
|
790
817
|
const DEFAULT_MODEL$1 = "text-embedding-3-small";
|
|
791
818
|
/**
|
|
792
819
|
* Known OpenAI embedding model dimensions. Extend as new models ship.
|
|
@@ -810,11 +837,20 @@ function resolveDimensions(model, override) {
|
|
|
810
837
|
/**
|
|
811
838
|
* OpenAI-compatible embedding provider.
|
|
812
839
|
*
|
|
840
|
+
* Shares transport (URL builder, auth header, Azure detection) with
|
|
841
|
+
* the OpenAI LLM provider via `_openai-shared` (#371). Same env knobs
|
|
842
|
+
* pick up automatically: when `OPENAI_BASE_URL` points at an Azure
|
|
843
|
+
* resource (`.openai.azure.com` hostname) the embedding request uses
|
|
844
|
+
* Azure's `/embeddings` path with the `api-version` query param and
|
|
845
|
+
* `api-key` header instead of `Authorization: Bearer`.
|
|
846
|
+
*
|
|
813
847
|
* Required env vars:
|
|
814
848
|
* OPENAI_API_KEY — API key
|
|
815
849
|
*
|
|
816
850
|
* Optional:
|
|
817
|
-
* OPENAI_BASE_URL — base URL without path (default: https://api.openai.com)
|
|
851
|
+
* OPENAI_BASE_URL — base URL without path (default: https://api.openai.com).
|
|
852
|
+
* Azure: https://<resource>.openai.azure.com/openai/deployments/<deployment>
|
|
853
|
+
* OPENAI_API_VERSION — Azure api-version query param (default: 2024-08-01-preview)
|
|
818
854
|
* OPENAI_EMBEDDING_MODEL — model name (default: text-embedding-3-small)
|
|
819
855
|
* OPENAI_EMBEDDING_DIMENSIONS — override reported dimensions (required for
|
|
820
856
|
* custom / self-hosted models not in the
|
|
@@ -826,24 +862,25 @@ var OpenAIEmbeddingProvider = class {
|
|
|
826
862
|
apiKey;
|
|
827
863
|
baseUrl;
|
|
828
864
|
model;
|
|
865
|
+
isAzure;
|
|
866
|
+
azureApiVersion;
|
|
829
867
|
constructor(apiKey) {
|
|
830
868
|
this.apiKey = apiKey || getEnvVar("OPENAI_API_KEY") || "";
|
|
831
869
|
if (!this.apiKey) throw new Error("OPENAI_API_KEY is required");
|
|
832
|
-
this.baseUrl = getEnvVar("OPENAI_BASE_URL")
|
|
870
|
+
this.baseUrl = normalizeBaseUrl(getEnvVar("OPENAI_BASE_URL"));
|
|
833
871
|
this.model = getEnvVar("OPENAI_EMBEDDING_MODEL") || DEFAULT_MODEL$1;
|
|
834
872
|
this.dimensions = resolveDimensions(this.model, getEnvVar("OPENAI_EMBEDDING_DIMENSIONS"));
|
|
873
|
+
this.isAzure = detectAzure(this.baseUrl);
|
|
874
|
+
this.azureApiVersion = getEnvVar("OPENAI_API_VERSION") || DEFAULT_AZURE_API_VERSION;
|
|
835
875
|
}
|
|
836
876
|
async embed(text) {
|
|
837
877
|
const [result] = await this.embedBatch([text]);
|
|
838
878
|
return result;
|
|
839
879
|
}
|
|
840
880
|
async embedBatch(texts) {
|
|
841
|
-
const response = await fetchWithTimeout(
|
|
881
|
+
const response = await fetchWithTimeout(buildEmbeddingUrl(this.baseUrl, this.isAzure, this.azureApiVersion), {
|
|
842
882
|
method: "POST",
|
|
843
|
-
headers:
|
|
844
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
845
|
-
"Content-Type": "application/json"
|
|
846
|
-
},
|
|
883
|
+
headers: buildAuthHeaders(this.apiKey, this.isAzure),
|
|
847
884
|
body: JSON.stringify({
|
|
848
885
|
model: this.model,
|
|
849
886
|
input: texts
|
|
@@ -1250,7 +1287,8 @@ const KV = {
|
|
|
1250
1287
|
imageEmbeddings: "mem:image-embeddings",
|
|
1251
1288
|
slots: "mem:slots",
|
|
1252
1289
|
globalSlots: "mem:slots:global",
|
|
1253
|
-
state: "mem:state"
|
|
1290
|
+
state: "mem:state",
|
|
1291
|
+
commits: "mem:commits"
|
|
1254
1292
|
};
|
|
1255
1293
|
const STREAM = {
|
|
1256
1294
|
name: "mem-live",
|
|
@@ -1444,7 +1482,7 @@ var GraphRetrieval = class {
|
|
|
1444
1482
|
const results = [];
|
|
1445
1483
|
const visitedObs = /* @__PURE__ */ new Set();
|
|
1446
1484
|
for (const startNode of matchingNodes) {
|
|
1447
|
-
const paths = this.
|
|
1485
|
+
const paths = this.dijkstraTraversal(startNode, allNodes, allEdges, maxDepth);
|
|
1448
1486
|
for (const path of paths) {
|
|
1449
1487
|
const lastNode = path[path.length - 1].node;
|
|
1450
1488
|
for (const obsId of lastNode.sourceObservationIds) {
|
|
@@ -1484,7 +1522,7 @@ var GraphRetrieval = class {
|
|
|
1484
1522
|
const results = [];
|
|
1485
1523
|
const visitedObs = new Set(obsIds);
|
|
1486
1524
|
for (const node of linkedNodes) {
|
|
1487
|
-
const paths = this.
|
|
1525
|
+
const paths = this.dijkstraTraversal(node, allNodes, allEdges, maxDepth);
|
|
1488
1526
|
for (const path of paths) {
|
|
1489
1527
|
const lastNode = path[path.length - 1].node;
|
|
1490
1528
|
for (const obsId of lastNode.sourceObservationIds) {
|
|
@@ -1556,37 +1594,104 @@ var GraphRetrieval = class {
|
|
|
1556
1594
|
}
|
|
1557
1595
|
return latest;
|
|
1558
1596
|
}
|
|
1559
|
-
|
|
1560
|
-
const
|
|
1561
|
-
const
|
|
1562
|
-
const
|
|
1597
|
+
dijkstraTraversal(startNode, allNodes, allEdges, maxDepth) {
|
|
1598
|
+
const nodeIndex = /* @__PURE__ */ new Map();
|
|
1599
|
+
for (const n of allNodes) nodeIndex.set(n.id, n);
|
|
1600
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
1601
|
+
for (const edge of allEdges) {
|
|
1602
|
+
const a = edge.sourceNodeId;
|
|
1603
|
+
const b = edge.targetNodeId;
|
|
1604
|
+
if (!adjacency.has(a)) adjacency.set(a, []);
|
|
1605
|
+
if (!adjacency.has(b)) adjacency.set(b, []);
|
|
1606
|
+
adjacency.get(a).push({
|
|
1607
|
+
neighborId: b,
|
|
1608
|
+
edge
|
|
1609
|
+
});
|
|
1610
|
+
adjacency.get(b).push({
|
|
1611
|
+
neighborId: a,
|
|
1612
|
+
edge
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
const dist = /* @__PURE__ */ new Map();
|
|
1616
|
+
const pathTo = /* @__PURE__ */ new Map();
|
|
1617
|
+
dist.set(startNode.id, 0);
|
|
1618
|
+
pathTo.set(startNode.id, [{ node: startNode }]);
|
|
1619
|
+
const heap = new MinHeap((a, b) => a.cost - b.cost);
|
|
1620
|
+
heap.push({
|
|
1563
1621
|
nodeId: startNode.id,
|
|
1564
1622
|
depth: 0,
|
|
1565
|
-
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
paths.push(path);
|
|
1623
|
+
cost: 0
|
|
1624
|
+
});
|
|
1625
|
+
while (heap.size() > 0) {
|
|
1626
|
+
const { nodeId, depth, cost } = heap.pop();
|
|
1627
|
+
if (cost > (dist.get(nodeId) ?? Infinity)) continue;
|
|
1571
1628
|
if (depth >= maxDepth) continue;
|
|
1572
|
-
const
|
|
1573
|
-
for (const edge of
|
|
1574
|
-
const
|
|
1575
|
-
if (visited.has(nextId)) continue;
|
|
1576
|
-
visited.add(nextId);
|
|
1577
|
-
const nextNode = allNodes.find((n) => n.id === nextId);
|
|
1629
|
+
const neighbors = adjacency.get(nodeId) ?? [];
|
|
1630
|
+
for (const { neighborId, edge } of neighbors) {
|
|
1631
|
+
const nextNode = nodeIndex.get(neighborId);
|
|
1578
1632
|
if (!nextNode) continue;
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1633
|
+
const newCost = cost + 1 / Math.max(edge.weight, .01);
|
|
1634
|
+
if (newCost < (dist.get(neighborId) ?? Infinity)) {
|
|
1635
|
+
dist.set(neighborId, newCost);
|
|
1636
|
+
pathTo.set(neighborId, [...pathTo.get(nodeId), {
|
|
1583
1637
|
node: nextNode,
|
|
1584
1638
|
edge
|
|
1585
|
-
}]
|
|
1586
|
-
|
|
1639
|
+
}]);
|
|
1640
|
+
heap.push({
|
|
1641
|
+
nodeId: neighborId,
|
|
1642
|
+
depth: depth + 1,
|
|
1643
|
+
cost: newCost
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1587
1646
|
}
|
|
1588
1647
|
}
|
|
1589
|
-
|
|
1648
|
+
pathTo.delete(startNode.id);
|
|
1649
|
+
return Array.from(pathTo.values());
|
|
1650
|
+
}
|
|
1651
|
+
};
|
|
1652
|
+
var MinHeap = class {
|
|
1653
|
+
heap = [];
|
|
1654
|
+
constructor(compare) {
|
|
1655
|
+
this.compare = compare;
|
|
1656
|
+
}
|
|
1657
|
+
size() {
|
|
1658
|
+
return this.heap.length;
|
|
1659
|
+
}
|
|
1660
|
+
push(value) {
|
|
1661
|
+
this.heap.push(value);
|
|
1662
|
+
this.bubbleUp(this.heap.length - 1);
|
|
1663
|
+
}
|
|
1664
|
+
pop() {
|
|
1665
|
+
if (this.heap.length === 0) return void 0;
|
|
1666
|
+
const top = this.heap[0];
|
|
1667
|
+
const last = this.heap.pop();
|
|
1668
|
+
if (this.heap.length > 0) {
|
|
1669
|
+
this.heap[0] = last;
|
|
1670
|
+
this.sinkDown(0);
|
|
1671
|
+
}
|
|
1672
|
+
return top;
|
|
1673
|
+
}
|
|
1674
|
+
bubbleUp(i) {
|
|
1675
|
+
while (i > 0) {
|
|
1676
|
+
const parent = i - 1 >> 1;
|
|
1677
|
+
if (this.compare(this.heap[i], this.heap[parent]) < 0) {
|
|
1678
|
+
[this.heap[i], this.heap[parent]] = [this.heap[parent], this.heap[i]];
|
|
1679
|
+
i = parent;
|
|
1680
|
+
} else break;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
sinkDown(i) {
|
|
1684
|
+
const n = this.heap.length;
|
|
1685
|
+
while (true) {
|
|
1686
|
+
const left = 2 * i + 1;
|
|
1687
|
+
const right = 2 * i + 2;
|
|
1688
|
+
let smallest = i;
|
|
1689
|
+
if (left < n && this.compare(this.heap[left], this.heap[smallest]) < 0) smallest = left;
|
|
1690
|
+
if (right < n && this.compare(this.heap[right], this.heap[smallest]) < 0) smallest = right;
|
|
1691
|
+
if (smallest === i) break;
|
|
1692
|
+
[this.heap[i], this.heap[smallest]] = [this.heap[smallest], this.heap[i]];
|
|
1693
|
+
i = smallest;
|
|
1694
|
+
}
|
|
1590
1695
|
}
|
|
1591
1696
|
};
|
|
1592
1697
|
|
|
@@ -6212,7 +6317,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
6212
6317
|
|
|
6213
6318
|
//#endregion
|
|
6214
6319
|
//#region src/version.ts
|
|
6215
|
-
const VERSION = "0.9.
|
|
6320
|
+
const VERSION = "0.9.20";
|
|
6216
6321
|
|
|
6217
6322
|
//#endregion
|
|
6218
6323
|
//#region src/functions/export-import.ts
|
|
@@ -6348,7 +6453,9 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
6348
6453
|
"0.9.15",
|
|
6349
6454
|
"0.9.16",
|
|
6350
6455
|
"0.9.17",
|
|
6351
|
-
"0.9.18"
|
|
6456
|
+
"0.9.18",
|
|
6457
|
+
"0.9.19",
|
|
6458
|
+
"0.9.20"
|
|
6352
6459
|
]).has(importData.version)) return {
|
|
6353
6460
|
success: false,
|
|
6354
6461
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -14176,6 +14283,112 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
14176
14283
|
middleware_function_ids: ["middleware::api-auth"]
|
|
14177
14284
|
}
|
|
14178
14285
|
});
|
|
14286
|
+
sdk.registerFunction("api::session::commit", async (req) => {
|
|
14287
|
+
const body = req.body ?? {};
|
|
14288
|
+
const sha = asNonEmptyString$1(body.sha);
|
|
14289
|
+
if (!sha) return {
|
|
14290
|
+
status_code: 400,
|
|
14291
|
+
body: { error: "sha is required and must be a non-empty string" }
|
|
14292
|
+
};
|
|
14293
|
+
const sessionId = asNonEmptyString$1(body.sessionId) ?? void 0;
|
|
14294
|
+
const branch = asNonEmptyString$1(body.branch) ?? void 0;
|
|
14295
|
+
const repo = asNonEmptyString$1(body.repo) ?? void 0;
|
|
14296
|
+
const message = asNonEmptyString$1(body.message) ?? void 0;
|
|
14297
|
+
const author = asNonEmptyString$1(body.author) ?? void 0;
|
|
14298
|
+
const authoredAt = asNonEmptyString$1(body.authoredAt) ?? void 0;
|
|
14299
|
+
const files = Array.isArray(body.files) ? body.files.filter((f) => typeof f === "string" && f.length > 0) : void 0;
|
|
14300
|
+
const link = await withKeyedLock(`commit:${sha}`, async () => {
|
|
14301
|
+
const existing = await kv.get(KV.commits, sha);
|
|
14302
|
+
const sessionSet = new Set(existing?.sessionIds ?? []);
|
|
14303
|
+
if (sessionId) sessionSet.add(sessionId);
|
|
14304
|
+
const merged = {
|
|
14305
|
+
sha,
|
|
14306
|
+
shortSha: existing?.shortSha ?? sha.slice(0, 7),
|
|
14307
|
+
branch: branch ?? existing?.branch,
|
|
14308
|
+
repo: repo ?? existing?.repo,
|
|
14309
|
+
message: message ?? existing?.message,
|
|
14310
|
+
author: author ?? existing?.author,
|
|
14311
|
+
authoredAt: authoredAt ?? existing?.authoredAt,
|
|
14312
|
+
files: files ?? existing?.files,
|
|
14313
|
+
sessionIds: Array.from(sessionSet),
|
|
14314
|
+
linkedAt: existing?.linkedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
14315
|
+
};
|
|
14316
|
+
await kv.set(KV.commits, sha, merged);
|
|
14317
|
+
return merged;
|
|
14318
|
+
});
|
|
14319
|
+
if (sessionId) await withKeyedLock(`session:${sessionId}`, async () => {
|
|
14320
|
+
const session = await kv.get(KV.sessions, sessionId);
|
|
14321
|
+
if (!session) return;
|
|
14322
|
+
const shaSet = new Set(session.commitShas ?? []);
|
|
14323
|
+
shaSet.add(sha);
|
|
14324
|
+
session.commitShas = Array.from(shaSet);
|
|
14325
|
+
await kv.set(KV.sessions, sessionId, session);
|
|
14326
|
+
});
|
|
14327
|
+
return {
|
|
14328
|
+
status_code: 200,
|
|
14329
|
+
body: { commit: link }
|
|
14330
|
+
};
|
|
14331
|
+
});
|
|
14332
|
+
sdk.registerTrigger({
|
|
14333
|
+
type: "http",
|
|
14334
|
+
function_id: "api::session::commit",
|
|
14335
|
+
config: {
|
|
14336
|
+
api_path: "/agentmemory/session/commit",
|
|
14337
|
+
http_method: "POST",
|
|
14338
|
+
middleware_function_ids: ["middleware::api-auth"]
|
|
14339
|
+
}
|
|
14340
|
+
});
|
|
14341
|
+
sdk.registerFunction("api::session::by-commit", async (req) => {
|
|
14342
|
+
const authErr = checkAuth(req, secret);
|
|
14343
|
+
if (authErr) return authErr;
|
|
14344
|
+
const sha = asNonEmptyString$1(req.query_params?.["sha"]);
|
|
14345
|
+
if (!sha) return {
|
|
14346
|
+
status_code: 400,
|
|
14347
|
+
body: { error: "sha is required and must be a non-empty string" }
|
|
14348
|
+
};
|
|
14349
|
+
const link = await kv.get(KV.commits, sha);
|
|
14350
|
+
if (!link) return {
|
|
14351
|
+
status_code: 404,
|
|
14352
|
+
body: { error: "no sessions linked to this commit" }
|
|
14353
|
+
};
|
|
14354
|
+
return {
|
|
14355
|
+
status_code: 200,
|
|
14356
|
+
body: {
|
|
14357
|
+
commit: link,
|
|
14358
|
+
sessions: (await Promise.all((link.sessionIds ?? []).map((sid) => kv.get(KV.sessions, sid)))).filter((s) => s !== null)
|
|
14359
|
+
}
|
|
14360
|
+
};
|
|
14361
|
+
});
|
|
14362
|
+
sdk.registerTrigger({
|
|
14363
|
+
type: "http",
|
|
14364
|
+
function_id: "api::session::by-commit",
|
|
14365
|
+
config: {
|
|
14366
|
+
api_path: "/agentmemory/session/by-commit",
|
|
14367
|
+
http_method: "GET",
|
|
14368
|
+
middleware_function_ids: ["middleware::api-auth"]
|
|
14369
|
+
}
|
|
14370
|
+
});
|
|
14371
|
+
sdk.registerFunction("api::commits", async (req) => {
|
|
14372
|
+
const authErr = checkAuth(req, secret);
|
|
14373
|
+
if (authErr) return authErr;
|
|
14374
|
+
const branch = asNonEmptyString$1(req.query_params?.["branch"]);
|
|
14375
|
+
const repo = asNonEmptyString$1(req.query_params?.["repo"]);
|
|
14376
|
+
const rawLimit = parseOptionalInt(req.query_params?.["limit"]);
|
|
14377
|
+
const limit = Math.max(1, Math.min(500, rawLimit ?? 100));
|
|
14378
|
+
return {
|
|
14379
|
+
status_code: 200,
|
|
14380
|
+
body: { commits: (await kv.list(KV.commits)).filter((c) => !branch || c.branch === branch).filter((c) => !repo || c.repo === repo).sort((a, b) => (a.linkedAt ?? "") < (b.linkedAt ?? "") ? 1 : -1).slice(0, limit) }
|
|
14381
|
+
};
|
|
14382
|
+
});
|
|
14383
|
+
sdk.registerTrigger({
|
|
14384
|
+
type: "http",
|
|
14385
|
+
function_id: "api::commits",
|
|
14386
|
+
config: {
|
|
14387
|
+
api_path: "/agentmemory/commits",
|
|
14388
|
+
http_method: "GET",
|
|
14389
|
+
middleware_function_ids: ["middleware::api-auth"]
|
|
14390
|
+
}
|
|
14391
|
+
});
|
|
14179
14392
|
sdk.registerFunction("api::sessions", async (req) => {
|
|
14180
14393
|
const authErr = checkAuth(req, secret);
|
|
14181
14394
|
if (authErr) return authErr;
|
|
@@ -17176,6 +17389,39 @@ const CORE_TOOLS = [
|
|
|
17176
17389
|
},
|
|
17177
17390
|
required: ["memoryId"]
|
|
17178
17391
|
}
|
|
17392
|
+
},
|
|
17393
|
+
{
|
|
17394
|
+
name: "memory_commit_lookup",
|
|
17395
|
+
description: "Look up the agent session(s) that produced a specific git commit, given its SHA. Returns the commit metadata and linked sessions.",
|
|
17396
|
+
inputSchema: {
|
|
17397
|
+
type: "object",
|
|
17398
|
+
properties: { sha: {
|
|
17399
|
+
type: "string",
|
|
17400
|
+
description: "Full git commit SHA"
|
|
17401
|
+
} },
|
|
17402
|
+
required: ["sha"]
|
|
17403
|
+
}
|
|
17404
|
+
},
|
|
17405
|
+
{
|
|
17406
|
+
name: "memory_commits",
|
|
17407
|
+
description: "List recent commits linked to agent sessions, optionally filtered by branch or repo.",
|
|
17408
|
+
inputSchema: {
|
|
17409
|
+
type: "object",
|
|
17410
|
+
properties: {
|
|
17411
|
+
branch: {
|
|
17412
|
+
type: "string",
|
|
17413
|
+
description: "Filter by branch name"
|
|
17414
|
+
},
|
|
17415
|
+
repo: {
|
|
17416
|
+
type: "string",
|
|
17417
|
+
description: "Filter by remote URL"
|
|
17418
|
+
},
|
|
17419
|
+
limit: {
|
|
17420
|
+
type: "number",
|
|
17421
|
+
description: "Max results (default 100, max 500)"
|
|
17422
|
+
}
|
|
17423
|
+
}
|
|
17424
|
+
}
|
|
17179
17425
|
}
|
|
17180
17426
|
];
|
|
17181
17427
|
const V040_TOOLS = [
|
|
@@ -19230,6 +19476,49 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
19230
19476
|
}] }
|
|
19231
19477
|
};
|
|
19232
19478
|
}
|
|
19479
|
+
case "memory_commit_lookup": {
|
|
19480
|
+
const sha = asNonEmptyString(args.sha);
|
|
19481
|
+
if (!sha) return {
|
|
19482
|
+
status_code: 400,
|
|
19483
|
+
body: { error: "sha required" }
|
|
19484
|
+
};
|
|
19485
|
+
const link = await kv.get(KV.commits, sha);
|
|
19486
|
+
if (!link) return {
|
|
19487
|
+
status_code: 200,
|
|
19488
|
+
body: { content: [{
|
|
19489
|
+
type: "text",
|
|
19490
|
+
text: JSON.stringify({
|
|
19491
|
+
commit: null,
|
|
19492
|
+
sessions: []
|
|
19493
|
+
}, null, 2)
|
|
19494
|
+
}] }
|
|
19495
|
+
};
|
|
19496
|
+
const linkRecord = link;
|
|
19497
|
+
const sessions = (await Promise.all((linkRecord.sessionIds ?? []).map((sid) => kv.get(KV.sessions, sid)))).filter((s) => s !== null);
|
|
19498
|
+
return {
|
|
19499
|
+
status_code: 200,
|
|
19500
|
+
body: { content: [{
|
|
19501
|
+
type: "text",
|
|
19502
|
+
text: JSON.stringify({
|
|
19503
|
+
commit: link,
|
|
19504
|
+
sessions
|
|
19505
|
+
}, null, 2)
|
|
19506
|
+
}] }
|
|
19507
|
+
};
|
|
19508
|
+
}
|
|
19509
|
+
case "memory_commits": {
|
|
19510
|
+
const branch = typeof args.branch === "string" ? args.branch : void 0;
|
|
19511
|
+
const repo = typeof args.repo === "string" ? args.repo : void 0;
|
|
19512
|
+
const limit = Math.max(1, Math.min(500, asNumber(args.limit, 100) ?? 100));
|
|
19513
|
+
const filtered = (await kv.list(KV.commits)).filter((c) => !branch || c.branch === branch).filter((c) => !repo || c.repo === repo).sort((a, b) => (a.linkedAt ?? "") < (b.linkedAt ?? "") ? 1 : -1).slice(0, limit);
|
|
19514
|
+
return {
|
|
19515
|
+
status_code: 200,
|
|
19516
|
+
body: { content: [{
|
|
19517
|
+
type: "text",
|
|
19518
|
+
text: JSON.stringify({ commits: filtered }, null, 2)
|
|
19519
|
+
}] }
|
|
19520
|
+
};
|
|
19521
|
+
}
|
|
19233
19522
|
default: return {
|
|
19234
19523
|
status_code: 400,
|
|
19235
19524
|
body: { error: `Unknown tool: ${name}` }
|
|
@@ -20142,7 +20431,7 @@ async function main() {
|
|
|
20142
20431
|
console.warn(`[agentmemory] Failed to backfill memories into BM25:`, err);
|
|
20143
20432
|
}
|
|
20144
20433
|
bootLog(`Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
|
|
20145
|
-
bootLog(`REST API:
|
|
20434
|
+
bootLog(`REST API: 124 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
|
|
20146
20435
|
bootLog(`MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 6 resources · 3 prompts`);
|
|
20147
20436
|
const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
|
|
20148
20437
|
const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
|