@ai-agent-forge/plugin-memory 0.88.0 → 0.88.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/agent-forge.json +1 -1
- package/bin/agent-forge-memory-inference.js +8 -0
- package/dist/capability.d.ts +42 -59
- package/dist/capability.d.ts.map +1 -1
- package/dist/capability.js +263 -2055
- package/dist/capability.js.map +1 -1
- package/dist/entry.d.ts +11 -0
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +38 -2
- package/dist/entry.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/inference-bin.d.ts +21 -0
- package/dist/inference-bin.d.ts.map +1 -0
- package/dist/inference-bin.js +85 -0
- package/dist/inference-bin.js.map +1 -0
- package/dist/memory/command-face.d.ts +86 -0
- package/dist/memory/command-face.d.ts.map +1 -0
- package/dist/memory/command-face.js +377 -0
- package/dist/memory/command-face.js.map +1 -0
- package/dist/memory/embedding-provider.d.ts.map +1 -1
- package/dist/memory/embedding-provider.js +14 -2
- package/dist/memory/embedding-provider.js.map +1 -1
- package/dist/memory/embedding-reranker.d.ts.map +1 -1
- package/dist/memory/embedding-reranker.js +14 -2
- package/dist/memory/embedding-reranker.js.map +1 -1
- package/dist/memory/enabling.d.ts +104 -0
- package/dist/memory/enabling.d.ts.map +1 -0
- package/dist/memory/enabling.js +321 -0
- package/dist/memory/enabling.js.map +1 -0
- package/dist/memory/hooks.d.ts +110 -0
- package/dist/memory/hooks.d.ts.map +1 -0
- package/dist/memory/hooks.js +478 -0
- package/dist/memory/hooks.js.map +1 -0
- package/dist/memory/inference-channel.d.ts +33 -0
- package/dist/memory/inference-channel.d.ts.map +1 -0
- package/dist/memory/inference-channel.js +101 -0
- package/dist/memory/inference-channel.js.map +1 -0
- package/dist/memory/inference-client.d.ts +25 -0
- package/dist/memory/inference-client.d.ts.map +1 -0
- package/dist/memory/inference-client.js +216 -0
- package/dist/memory/inference-client.js.map +1 -0
- package/dist/memory/inference-protocol.d.ts +95 -0
- package/dist/memory/inference-protocol.d.ts.map +1 -0
- package/dist/memory/inference-protocol.js +27 -0
- package/dist/memory/inference-protocol.js.map +1 -0
- package/dist/memory/inference-server.d.ts +70 -0
- package/dist/memory/inference-server.d.ts.map +1 -0
- package/dist/memory/inference-server.js +395 -0
- package/dist/memory/inference-server.js.map +1 -0
- package/dist/memory/inference-service-main.d.ts +31 -0
- package/dist/memory/inference-service-main.d.ts.map +1 -0
- package/dist/memory/inference-service-main.js +89 -0
- package/dist/memory/inference-service-main.js.map +1 -0
- package/dist/memory/ledger.d.ts +15 -0
- package/dist/memory/ledger.d.ts.map +1 -1
- package/dist/memory/ledger.js +35 -2
- package/dist/memory/ledger.js.map +1 -1
- package/dist/memory/model-cache-hygiene.d.ts +10 -0
- package/dist/memory/model-cache-hygiene.d.ts.map +1 -1
- package/dist/memory/model-cache-hygiene.js +15 -0
- package/dist/memory/model-cache-hygiene.js.map +1 -1
- package/dist/memory/preference-promotion.d.ts +0 -14
- package/dist/memory/preference-promotion.d.ts.map +1 -1
- package/dist/memory/preference-promotion.js +17 -0
- package/dist/memory/preference-promotion.js.map +1 -1
- package/dist/memory/recall-pipeline.d.ts +85 -0
- package/dist/memory/recall-pipeline.d.ts.map +1 -0
- package/dist/memory/recall-pipeline.js +678 -0
- package/dist/memory/recall-pipeline.js.map +1 -0
- package/dist/memory/retention-sweep.d.ts +59 -0
- package/dist/memory/retention-sweep.d.ts.map +1 -0
- package/dist/memory/retention-sweep.js +103 -0
- package/dist/memory/retention-sweep.js.map +1 -0
- package/dist/memory/store.d.ts +17 -2
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js +18 -2
- package/dist/memory/store.js.map +1 -1
- package/dist/memory/vector-channel.d.ts +120 -0
- package/dist/memory/vector-channel.d.ts.map +1 -0
- package/dist/memory/vector-channel.js +495 -0
- package/dist/memory/vector-channel.js.map +1 -0
- package/dist/memory/vector-projection.d.ts +87 -0
- package/dist/memory/vector-projection.d.ts.map +1 -0
- package/dist/memory/vector-projection.js +215 -0
- package/dist/memory/vector-projection.js.map +1 -0
- package/dist/memory/write-face.d.ts +128 -0
- package/dist/memory/write-face.d.ts.map +1 -0
- package/dist/memory/write-face.js +504 -0
- package/dist/memory/write-face.js.map +1 -0
- package/dist/testing/memory-testkit.d.ts +2 -0
- package/dist/testing/memory-testkit.d.ts.map +1 -1
- package/dist/testing/memory-testkit.js +1 -0
- package/dist/testing/memory-testkit.js.map +1 -1
- package/package.json +7 -3
- package/plugin.json +1 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 推理服务通道生命周期(capability 拆分战役 slice 2):懒拉起、连接重试预算与失败行动清单。共享状态经 deps 显式注入, 无模块级可变状态。
|
|
3
|
+
*/
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { connectMemoryInferenceClient } from "./inference-client.js";
|
|
8
|
+
import { MEMORY_INFERENCE_PORT_ENV } from "./inference-protocol.js";
|
|
9
|
+
/**
|
|
10
|
+
* 懒拉起共享推理服务进程 (Slice B2, 设计 §3.5): detached + unref — 服务独立于
|
|
11
|
+
* 拉起它的会话存活 (TUI 退出不拽走), 空闲自杀由服务自身承担; 拉起方在预算内
|
|
12
|
+
* 轮询重连。bin 形态使宿主零改动 (B1); src 与 dist 布局同构 (包根下一层),
|
|
13
|
+
* 相对定位两条形态通用。
|
|
14
|
+
*/
|
|
15
|
+
export function spawnInferenceServiceProcess(agentDir, mode, port,
|
|
16
|
+
/** spawn 异步失败回调。无监听器的 'error' 事件按 Node 语义升级为
|
|
17
|
+
* uncaughtException 崩掉会话进程, 故即使调用方不关心也必须挂上 once。 */
|
|
18
|
+
onError) {
|
|
19
|
+
// src 与 dist 布局同构 (本模块在包根下两层 src/memory/, bin 在包根下):
|
|
20
|
+
// 2026-10-06 live E2E 修正 — capability.ts 时代 "../../bin" 曾差一层解析到
|
|
21
|
+
// plugins/bin (deterministic 套件注入 spawnImpl 缝, 从未真解析过路径, 只有
|
|
22
|
+
// 真实懒拉起暴露); 拆分搬移到 src/memory/ 后相对深度同步 +1。
|
|
23
|
+
const binPath = fileURLToPath(new URL("../../bin/agent-forge-memory-inference.js", import.meta.url));
|
|
24
|
+
if (!existsSync(binPath)) {
|
|
25
|
+
throw new Error(`memory inference service entry not found at ${binPath} (plugin installation incomplete)`);
|
|
26
|
+
}
|
|
27
|
+
const child = spawn(process.execPath, [binPath], {
|
|
28
|
+
detached: true,
|
|
29
|
+
stdio: "ignore",
|
|
30
|
+
windowsHide: true,
|
|
31
|
+
env: {
|
|
32
|
+
...process.env,
|
|
33
|
+
AGENT_FORGE_MEMORY_AGENT_DIR: agentDir,
|
|
34
|
+
AGENT_FORGE_MEMORY_MODE: mode,
|
|
35
|
+
AGENT_FORGE_MEMORY_INFERENCE_PORT: String(port),
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
// spawn 失败是异步的 (EACCES/ENOMEM 在 spawn() 返回后才到达): 'error' 事件
|
|
39
|
+
// 无监听器 = uncaughtException; once 足够, 首个错误即结论。
|
|
40
|
+
child.once("error", (error) => onError?.(error));
|
|
41
|
+
child.unref();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 服务装配失败的行动清单 (设计 §4: 行动提示面向服务, 不是本地装配的镜像/
|
|
45
|
+
* 依赖建议 — 模型已在位时那五条是误导)。
|
|
46
|
+
*/
|
|
47
|
+
export function serviceFailureActionList(port) {
|
|
48
|
+
return [
|
|
49
|
+
`Wait for the shared memory inference service (port ${port}) to become reachable, then restart the session — a failed assembly does not retry inside this session`,
|
|
50
|
+
`If another process occupies the port, set ${MEMORY_INFERENCE_PORT_ENV} to a free port and restart the session`,
|
|
51
|
+
`Run the plugin bin manually to see its structured startup error (missing models or port conflict): agent-forge-memory-inference`,
|
|
52
|
+
'Give up: leave memory.mode "off" in settings.json so the memory family stays unregistered',
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 会话装配 (设计 §3.3): 模型在位 (或经服务缝声明在位) 且无本地装配测试缝时,
|
|
57
|
+
* 向量通道 = 共享推理服务的 IPC 客户端组件; 失败按 enabling failed 呈现,
|
|
58
|
+
* 行动清单面向服务 (不回退本地装配 — 回退即重开多进程风险, 裁决 A)。
|
|
59
|
+
*/
|
|
60
|
+
export async function ensureInferenceServiceChannel(deps) {
|
|
61
|
+
const connectOnce = () => connectMemoryInferenceClient({ port: deps.port, connectTimeoutMs: 1500, requestTimeoutMs: 60_000 });
|
|
62
|
+
// 只把连接层失败 (拒绝/连接超时) 当"服务未起"触发懒拉起重试; 请求层超时
|
|
63
|
+
// (hello 已握手但不应答, 如端口被无关进程占用) 属握手层错误, 直接上抛 —
|
|
64
|
+
// 否则先吃满请求超时再空转拉起, 终错文案偏离根因。
|
|
65
|
+
const isRefusal = (message) => message.includes("unreachable") || (message.includes("connect to") && message.includes("timed out"));
|
|
66
|
+
try {
|
|
67
|
+
return await connectOnce();
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
71
|
+
if (!isRefusal(message))
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
const spawnService = deps.spawnImpl ?? spawnInferenceServiceProcess;
|
|
75
|
+
// spawn 异步失败 (EACCES/ENOMEM) 经 onError 到达: 该文案不含
|
|
76
|
+
// "unreachable"/"connect to ... timed out", 不命中 isRefusal, 直通上层
|
|
77
|
+
// failEnabling 的精准根因, 也不必空转满 15s 重连预算。
|
|
78
|
+
let spawnError;
|
|
79
|
+
await spawnService(deps.agentDir, deps.memoryMode, deps.port, (error) => {
|
|
80
|
+
spawnError ??= error;
|
|
81
|
+
});
|
|
82
|
+
const deadline = Date.now() + 15_000;
|
|
83
|
+
for (;;) {
|
|
84
|
+
if (spawnError !== undefined) {
|
|
85
|
+
throw new Error(`inference service process failed to spawn: ${spawnError.message}`);
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
return await connectOnce();
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
92
|
+
if (!isRefusal(message))
|
|
93
|
+
throw error;
|
|
94
|
+
if (Date.now() >= deadline) {
|
|
95
|
+
throw new Error(`inference service not reachable on port ${deps.port} within 15s of spawn`);
|
|
96
|
+
}
|
|
97
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=inference-channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inference-channel.js","sourceRoot":"","sources":["../../src/memory/inference-channel.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 推理服务通道生命周期(capability 拆分战役 slice 2):懒拉起、连接重试预算与失败行动清单。共享状态经 deps 显式注入, 无模块级可变状态。\n */\n\nimport { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport type { MemoryModeV1 } from \"@agent-forge/plugin-sdk\";\nimport { connectMemoryInferenceClient, type MemoryInferenceClientV1 } from \"./inference-client.ts\";\nimport { MEMORY_INFERENCE_PORT_ENV } from \"./inference-protocol.ts\";\n\n/**\n * 懒拉起共享推理服务进程 (Slice B2, 设计 §3.5): detached + unref — 服务独立于\n * 拉起它的会话存活 (TUI 退出不拽走), 空闲自杀由服务自身承担; 拉起方在预算内\n * 轮询重连。bin 形态使宿主零改动 (B1); src 与 dist 布局同构 (包根下一层),\n * 相对定位两条形态通用。\n */\nexport function spawnInferenceServiceProcess(\n\tagentDir: string,\n\tmode: Exclude<MemoryModeV1, \"off\">,\n\tport: number,\n\t/** spawn 异步失败回调。无监听器的 'error' 事件按 Node 语义升级为\n\t * uncaughtException 崩掉会话进程, 故即使调用方不关心也必须挂上 once。 */\n\tonError?: (error: Error) => void,\n): void {\n\t// src 与 dist 布局同构 (本模块在包根下两层 src/memory/, bin 在包根下):\n\t// 2026-10-06 live E2E 修正 — capability.ts 时代 \"../../bin\" 曾差一层解析到\n\t// plugins/bin (deterministic 套件注入 spawnImpl 缝, 从未真解析过路径, 只有\n\t// 真实懒拉起暴露); 拆分搬移到 src/memory/ 后相对深度同步 +1。\n\tconst binPath = fileURLToPath(new URL(\"../../bin/agent-forge-memory-inference.js\", import.meta.url));\n\tif (!existsSync(binPath)) {\n\t\tthrow new Error(`memory inference service entry not found at ${binPath} (plugin installation incomplete)`);\n\t}\n\tconst child = spawn(process.execPath, [binPath], {\n\t\tdetached: true,\n\t\tstdio: \"ignore\",\n\t\twindowsHide: true,\n\t\tenv: {\n\t\t\t...process.env,\n\t\t\tAGENT_FORGE_MEMORY_AGENT_DIR: agentDir,\n\t\t\tAGENT_FORGE_MEMORY_MODE: mode,\n\t\t\tAGENT_FORGE_MEMORY_INFERENCE_PORT: String(port),\n\t\t},\n\t});\n\t// spawn 失败是异步的 (EACCES/ENOMEM 在 spawn() 返回后才到达): 'error' 事件\n\t// 无监听器 = uncaughtException; once 足够, 首个错误即结论。\n\tchild.once(\"error\", (error) => onError?.(error));\n\tchild.unref();\n}\n\n/**\n * 服务装配失败的行动清单 (设计 §4: 行动提示面向服务, 不是本地装配的镜像/\n * 依赖建议 — 模型已在位时那五条是误导)。\n */\nexport function serviceFailureActionList(port: number): readonly string[] {\n\treturn [\n\t\t`Wait for the shared memory inference service (port ${port}) to become reachable, then restart the session — a failed assembly does not retry inside this session`,\n\t\t`If another process occupies the port, set ${MEMORY_INFERENCE_PORT_ENV} to a free port and restart the session`,\n\t\t`Run the plugin bin manually to see its structured startup error (missing models or port conflict): agent-forge-memory-inference`,\n\t\t'Give up: leave memory.mode \"off\" in settings.json so the memory family stays unregistered',\n\t];\n}\n\nexport interface MemoryInferenceChannelDepsV1 {\n\treadonly agentDir: string;\n\treadonly memoryMode: \"light\" | \"full\";\n\treadonly port: number;\n\treadonly spawnImpl?: (\n\t\tagentDir: string,\n\t\tmode: \"light\" | \"full\",\n\t\tport: number,\n\t\tonError?: (error: Error) => void,\n\t) => Promise<void>;\n}\n\n/**\n * 会话装配 (设计 §3.3): 模型在位 (或经服务缝声明在位) 且无本地装配测试缝时,\n * 向量通道 = 共享推理服务的 IPC 客户端组件; 失败按 enabling failed 呈现,\n * 行动清单面向服务 (不回退本地装配 — 回退即重开多进程风险, 裁决 A)。\n */\nexport async function ensureInferenceServiceChannel(\n\tdeps: MemoryInferenceChannelDepsV1,\n): Promise<MemoryInferenceClientV1> {\n\tconst connectOnce = () =>\n\t\tconnectMemoryInferenceClient({ port: deps.port, connectTimeoutMs: 1500, requestTimeoutMs: 60_000 });\n\t// 只把连接层失败 (拒绝/连接超时) 当\"服务未起\"触发懒拉起重试; 请求层超时\n\t// (hello 已握手但不应答, 如端口被无关进程占用) 属握手层错误, 直接上抛 —\n\t// 否则先吃满请求超时再空转拉起, 终错文案偏离根因。\n\tconst isRefusal = (message: string): boolean =>\n\t\tmessage.includes(\"unreachable\") || (message.includes(\"connect to\") && message.includes(\"timed out\"));\n\ttry {\n\t\treturn await connectOnce();\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tif (!isRefusal(message)) throw error;\n\t}\n\tconst spawnService = deps.spawnImpl ?? spawnInferenceServiceProcess;\n\t// spawn 异步失败 (EACCES/ENOMEM) 经 onError 到达: 该文案不含\n\t// \"unreachable\"/\"connect to ... timed out\", 不命中 isRefusal, 直通上层\n\t// failEnabling 的精准根因, 也不必空转满 15s 重连预算。\n\tlet spawnError: Error | undefined;\n\tawait spawnService(deps.agentDir, deps.memoryMode, deps.port, (error) => {\n\t\tspawnError ??= error;\n\t});\n\tconst deadline = Date.now() + 15_000;\n\tfor (;;) {\n\t\tif (spawnError !== undefined) {\n\t\t\tthrow new Error(`inference service process failed to spawn: ${spawnError.message}`);\n\t\t}\n\t\ttry {\n\t\t\treturn await connectOnce();\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tif (!isRefusal(message)) throw error;\n\t\t\tif (Date.now() >= deadline) {\n\t\t\t\tthrow new Error(`inference service not reachable on port ${deps.port} within 15s of spawn`);\n\t\t\t}\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 250));\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MemoryEmbeddingProviderV1 } from "./embedding-provider.ts";
|
|
2
|
+
import type { MemoryRerankerV1 } from "./embedding-reranker.ts";
|
|
3
|
+
import { type MemoryInferenceHelloResultV1 } from "./inference-protocol.ts";
|
|
4
|
+
import type { MemoryVectorIndexV1 } from "./vector-index.ts";
|
|
5
|
+
export interface MemoryInferenceClientOptions {
|
|
6
|
+
readonly port?: number;
|
|
7
|
+
readonly host?: string;
|
|
8
|
+
/** 单请求超时; 缺省 10s (出厂默认值, 登记产品默认值清单)。 */
|
|
9
|
+
readonly requestTimeoutMs?: number;
|
|
10
|
+
readonly connectTimeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface MemoryInferenceClientV1 {
|
|
13
|
+
readonly hello: MemoryInferenceHelloResultV1;
|
|
14
|
+
readonly embedProvider: MemoryEmbeddingProviderV1;
|
|
15
|
+
readonly reranker: MemoryRerankerV1 | undefined;
|
|
16
|
+
readonly vectorIndex: MemoryVectorIndexV1;
|
|
17
|
+
close(): Promise<void>;
|
|
18
|
+
/** 连接断开 (服务死亡/网络故障) 后 resolve — 调用方可据此触发重拉起。 */
|
|
19
|
+
readonly closed: Promise<void>;
|
|
20
|
+
/** 连接存活探测 (Slice B2 断连自愈用): socket 已建立且未进入关闭态。 */
|
|
21
|
+
isAlive(): boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare function resolveMemoryInferencePort(explicit?: number, env?: NodeJS.ProcessEnv): number;
|
|
24
|
+
export declare function connectMemoryInferenceClient(options?: MemoryInferenceClientOptions): Promise<MemoryInferenceClientV1>;
|
|
25
|
+
//# sourceMappingURL=inference-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inference-client.d.ts","sourceRoot":"","sources":["../../src/memory/inference-client.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAON,KAAK,4BAA4B,EAGjC,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAA+C,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE1G,MAAM,WAAW,4BAA4B;IAC5C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,oFAAwC;IACxC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,KAAK,EAAE,4BAA4B,CAAC;IAC7C,QAAQ,CAAC,aAAa,EAAE,yBAAyB,CAAC;IAClD,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;IAC1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,oGAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,8FAAkD;IAClD,OAAO,IAAI,OAAO,CAAC;CACnB;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAS1G;AAED,wBAAsB,4BAA4B,CACjD,OAAO,GAAE,4BAAiC,GACxC,OAAO,CAAC,uBAAuB,CAAC,CAuMlC","sourcesContent":["/**\n * 记忆共享推理服务 — 客户端组件 (记忆共享推理服务设计 §3.3)。\n *\n * 把 IPC 客户端包装成既有 `MemoryVectorComponentsOverrideV1` 形状 (embedProvider\n * / reranker / vectorIndex): 经 `HostCapabilities.memoryStorage.vectorComponents`\n * 注入后, memory capability 的向量操作透明变远程 — 插件契约零改动。会话进程\n * (主会话与 subagent runner) 由此共享服务进程里的一份模型与索引, 不再各自\n * 装配 (裁决 C 根治: 模型一份、LanceDB 单写者)。\n *\n * 失败语义 (设计 §4, 绑定裁决 A「宁漏勿错」): 服务不可达/握手失败/调用超时\n * 抛结构化错误 — 调用方的降级阶梯把它当通道故障处理 (auto-recall 零注入,\n * 显式召回 unavailable); 不回退本地装配 (回退即重开多进程风险)。\n */\nimport { connect, type Socket } from \"node:net\";\nimport { FrameDecoder } from \"@agent-forge/protocol\";\nimport type { MemoryEmbeddingProviderV1 } from \"./embedding-provider.ts\";\nimport type { MemoryRerankerV1 } from \"./embedding-reranker.ts\";\nimport {\n\tdecodeInferenceMessage,\n\tencodeInferenceMessage,\n\tMEMORY_INFERENCE_DEFAULT_HOST,\n\tMEMORY_INFERENCE_DEFAULT_PORT,\n\tMEMORY_INFERENCE_PORT_ENV,\n\tMEMORY_INFERENCE_PROTOCOL_VERSION,\n\ttype MemoryInferenceHelloResultV1,\n\ttype MemoryInferenceRequestV1,\n\ttype MemoryInferenceResponseV1,\n} from \"./inference-protocol.ts\";\nimport type { MemoryVectorHitV1, MemoryVectorIndexQueryV1, MemoryVectorIndexV1 } from \"./vector-index.ts\";\n\nexport interface MemoryInferenceClientOptions {\n\treadonly port?: number;\n\treadonly host?: string;\n\t/** 单请求超时; 缺省 10s (出厂默认值, 登记产品默认值清单)。 */\n\treadonly requestTimeoutMs?: number;\n\treadonly connectTimeoutMs?: number;\n}\n\nexport interface MemoryInferenceClientV1 {\n\treadonly hello: MemoryInferenceHelloResultV1;\n\treadonly embedProvider: MemoryEmbeddingProviderV1;\n\treadonly reranker: MemoryRerankerV1 | undefined;\n\treadonly vectorIndex: MemoryVectorIndexV1;\n\tclose(): Promise<void>;\n\t/** 连接断开 (服务死亡/网络故障) 后 resolve — 调用方可据此触发重拉起。 */\n\treadonly closed: Promise<void>;\n\t/** 连接存活探测 (Slice B2 断连自愈用): socket 已建立且未进入关闭态。 */\n\tisAlive(): boolean;\n}\n\nexport function resolveMemoryInferencePort(explicit?: number, env: NodeJS.ProcessEnv = process.env): number {\n\tif (explicit !== undefined) return explicit;\n\tconst raw = env[MEMORY_INFERENCE_PORT_ENV];\n\tif (raw !== undefined) {\n\t\tconst parsed = Number.parseInt(raw, 10);\n\t\tif (Number.isSafeInteger(parsed) && parsed > 0 && parsed <= 0xffff) return parsed;\n\t\tthrow new Error(`${MEMORY_INFERENCE_PORT_ENV} must be a TCP port number (got ${raw})`);\n\t}\n\treturn MEMORY_INFERENCE_DEFAULT_PORT;\n}\n\nexport async function connectMemoryInferenceClient(\n\toptions: MemoryInferenceClientOptions = {},\n): Promise<MemoryInferenceClientV1> {\n\tconst host = options.host ?? MEMORY_INFERENCE_DEFAULT_HOST;\n\tconst port = resolveMemoryInferencePort(options.port);\n\tconst requestTimeoutMs = options.requestTimeoutMs ?? 10_000;\n\tconst connectTimeoutMs = options.connectTimeoutMs ?? 5_000;\n\n\tlet requestSequence = 0;\n\tlet closed = false;\n\tlet closedResolve: (() => void) | undefined;\n\tconst closedPromise = new Promise<void>((resolve) => {\n\t\tclosedResolve = resolve;\n\t});\n\tinterface Pending {\n\t\tresolve(result: MemoryInferenceResponseV1): void;\n\t\treject(error: Error): void;\n\t\ttimer: NodeJS.Timeout;\n\t}\n\tconst pending = new Map<number, Pending>();\n\tlet socket!: Socket;\n\n\tconst failAll = (message: string): void => {\n\t\tfor (const [id, entry] of pending) {\n\t\t\tclearTimeout(entry.timer);\n\t\t\tpending.delete(id);\n\t\t\tentry.reject(new Error(message));\n\t\t}\n\t};\n\n\tconst request = async <T>(kind: MemoryInferenceRequestV1[\"kind\"], fields: Record<string, unknown>): Promise<T> => {\n\t\tif (closed) throw new Error(\"memory inference client is closed\");\n\t\tconst requestId = ++requestSequence;\n\t\tconst message = { kind, requestId, ...fields } as unknown as MemoryInferenceRequestV1;\n\t\treturn await new Promise<T>((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tpending.delete(requestId);\n\t\t\t\treject(new Error(`memory inference request \"${kind}\" timed out after ${requestTimeoutMs}ms`));\n\t\t\t}, requestTimeoutMs);\n\t\t\ttimer.unref?.();\n\t\t\tpending.set(requestId, {\n\t\t\t\tresolve: (response) => {\n\t\t\t\t\tif (response.kind === \"ok\") {\n\t\t\t\t\t\tresolve(response.result as T);\n\t\t\t\t\t} else {\n\t\t\t\t\t\treject(new Error(`memory inference \"${kind}\" failed (${response.code}): ${response.message}`));\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\treject,\n\t\t\t\ttimer,\n\t\t\t});\n\t\t\tsocket.write(encodeInferenceMessage(message), (error) => {\n\t\t\t\t// Node's write callback receives null on success (not undefined).\n\t\t\t\tif (error != null) {\n\t\t\t\t\tconst entry = pending.get(requestId);\n\t\t\t\t\tif (entry !== undefined) {\n\t\t\t\t\t\tclearTimeout(entry.timer);\n\t\t\t\t\t\tpending.delete(requestId);\n\t\t\t\t\t\tentry.reject(error instanceof Error ? error : new Error(String(error)));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t};\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tsocket = connect({ port, host });\n\t\tconst onConnectError = (error: Error): void => {\n\t\t\tclearTimeout(connectTimer);\n\t\t\treject(new Error(`memory inference server unreachable at ${host}:${port}: ${error.message}`));\n\t\t};\n\t\tconst connectTimer = setTimeout(() => {\n\t\t\tsocket.destroy();\n\t\t\treject(new Error(`memory inference connect to ${host}:${port} timed out after ${connectTimeoutMs}ms`));\n\t\t}, connectTimeoutMs);\n\t\tconnectTimer.unref?.();\n\t\tsocket.once(\"error\", onConnectError);\n\t\tsocket.once(\"connect\", () => {\n\t\t\tclearTimeout(connectTimer);\n\t\t\tsocket.off(\"error\", onConnectError);\n\t\t\tresolve();\n\t\t});\n\t});\n\n\tconst decoder = new FrameDecoder();\n\tsocket.on(\"data\", (chunk: Buffer) => {\n\t\t// 帧层异常必须捕获 (复审 P2-1): 超限帧抛 FrameError 即 uncaughtException\n\t\t// 会崩掉整个会话进程 — 断连并让 pending 结构化失败。\n\t\tlet frames: Uint8Array[];\n\t\ttry {\n\t\t\tframes = decoder.push(new Uint8Array(chunk));\n\t\t} catch {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tfor (const frame of frames) {\n\t\t\tif (frame.byteLength === 0) continue;\n\t\t\ttry {\n\t\t\t\tconst response = decodeInferenceMessage(frame) as MemoryInferenceResponseV1;\n\t\t\t\tif (response.kind === \"ok\" || response.kind === \"error\") {\n\t\t\t\t\tconst entry = pending.get(response.requestId);\n\t\t\t\t\tif (entry !== undefined) {\n\t\t\t\t\t\tclearTimeout(entry.timer);\n\t\t\t\t\t\tpending.delete(response.requestId);\n\t\t\t\t\t\tentry.resolve(response);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// decode failure: leave the request to time out (bounded by requestTimeoutMs)\n\t\t\t}\n\t\t}\n\t});\n\tsocket.on(\"error\", () => {\n\t\tfailAll(\"memory inference connection failed\");\n\t});\n\tsocket.on(\"close\", () => {\n\t\tclosed = true;\n\t\tfailAll(\"memory inference connection closed\");\n\t\tclosedResolve?.();\n\t});\n\n\t// hello 失败路径必须断开 (复审 P2-2): 否则 socket 泄漏且抑制服务端空闲自杀。\n\tconst hello = await request<MemoryInferenceHelloResultV1>(\"hello\", {\n\t\tprotocolVersion: MEMORY_INFERENCE_PROTOCOL_VERSION,\n\t}).catch((error: unknown) => {\n\t\tsocket.destroy();\n\t\tthrow error instanceof Error ? error : new Error(String(error));\n\t});\n\tif (hello.protocolVersion !== MEMORY_INFERENCE_PROTOCOL_VERSION) {\n\t\tsocket.destroy();\n\t\tthrow new Error(\n\t\t\t`memory inference protocol version mismatch: client=${MEMORY_INFERENCE_PROTOCOL_VERSION} server=${hello.protocolVersion}`,\n\t\t);\n\t}\n\n\tconst embedProvider: MemoryEmbeddingProviderV1 = {\n\t\tmodelId: hello.embeddingModelId,\n\t\tdimensions: hello.dimensions,\n\t\tembed: async (texts) => {\n\t\t\tconst result = await request<{ vectors: readonly (readonly number[])[] }>(\"embed\", { texts });\n\t\t\treturn result.vectors;\n\t\t},\n\t};\n\n\tconst reranker: MemoryRerankerV1 | undefined =\n\t\thello.rerankerModelId === null\n\t\t\t? undefined\n\t\t\t: {\n\t\t\t\t\tmodelId: hello.rerankerModelId,\n\t\t\t\t\trerank: async (query, docs) => {\n\t\t\t\t\t\tconst result = await request<{ scores: readonly number[] }>(\"rerank\", { query, docs });\n\t\t\t\t\t\treturn result.scores;\n\t\t\t\t\t},\n\t\t\t\t};\n\n\tconst vectorIndex: MemoryVectorIndexV1 = {\n\t\treplicaId: \"memory-vector-index\",\n\t\tupsert: async (entries) => {\n\t\t\tawait request(\"upsert\", { entries });\n\t\t},\n\t\tremove: async (memoryIds) => {\n\t\t\tawait request(\"remove\", { memoryIds });\n\t\t},\n\t\tqueryKnn: async (vector, query: MemoryVectorIndexQueryV1) => {\n\t\t\tconst result = await request<{ hits: readonly MemoryVectorHitV1[] }>(\"queryKnn\", { vector, query });\n\t\t\treturn result.hits;\n\t\t},\n\t\tqueryFts: async (queryText, query: MemoryVectorIndexQueryV1) => {\n\t\t\tconst result = await request<{ hits: readonly MemoryVectorHitV1[] }>(\"queryFts\", { queryText, query });\n\t\t\treturn result.hits;\n\t\t},\n\t\tlistMemoryIds: async () => {\n\t\t\tconst result = await request<{ memoryIds: readonly string[] }>(\"listMemoryIds\", {});\n\t\t\treturn new Set(result.memoryIds);\n\t\t},\n\t\tcount: async () => {\n\t\t\tconst result = await request<{ count: number }>(\"count\", {});\n\t\t\treturn result.count;\n\t\t},\n\t\tgetStoredModelId: async () => {\n\t\t\tconst result = await request<{ modelId: string | null }>(\"getStoredModelId\", {});\n\t\t\t// IPC payloads carry null; the local interface speaks undefined.\n\t\t\treturn result.modelId ?? undefined;\n\t\t},\n\t\tclose: async () => {\n\t\t\t// 客户端组件的 close 只断开本连接; 服务端索引归服务进程持有。\n\t\t\tsocket.destroy();\n\t\t},\n\t};\n\n\treturn {\n\t\thello,\n\t\tembedProvider,\n\t\treranker,\n\t\tvectorIndex,\n\t\tclose: async () => {\n\t\t\tsocket.destroy();\n\t\t},\n\t\tclosed: closedPromise,\n\t\tisAlive: () => !closed && !socket.destroyed,\n\t};\n}\n"]}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 记忆共享推理服务 — 客户端组件 (记忆共享推理服务设计 §3.3)。
|
|
3
|
+
*
|
|
4
|
+
* 把 IPC 客户端包装成既有 `MemoryVectorComponentsOverrideV1` 形状 (embedProvider
|
|
5
|
+
* / reranker / vectorIndex): 经 `HostCapabilities.memoryStorage.vectorComponents`
|
|
6
|
+
* 注入后, memory capability 的向量操作透明变远程 — 插件契约零改动。会话进程
|
|
7
|
+
* (主会话与 subagent runner) 由此共享服务进程里的一份模型与索引, 不再各自
|
|
8
|
+
* 装配 (裁决 C 根治: 模型一份、LanceDB 单写者)。
|
|
9
|
+
*
|
|
10
|
+
* 失败语义 (设计 §4, 绑定裁决 A「宁漏勿错」): 服务不可达/握手失败/调用超时
|
|
11
|
+
* 抛结构化错误 — 调用方的降级阶梯把它当通道故障处理 (auto-recall 零注入,
|
|
12
|
+
* 显式召回 unavailable); 不回退本地装配 (回退即重开多进程风险)。
|
|
13
|
+
*/
|
|
14
|
+
import { connect } from "node:net";
|
|
15
|
+
import { FrameDecoder } from "@agent-forge/protocol";
|
|
16
|
+
import { decodeInferenceMessage, encodeInferenceMessage, MEMORY_INFERENCE_DEFAULT_HOST, MEMORY_INFERENCE_DEFAULT_PORT, MEMORY_INFERENCE_PORT_ENV, MEMORY_INFERENCE_PROTOCOL_VERSION, } from "./inference-protocol.js";
|
|
17
|
+
export function resolveMemoryInferencePort(explicit, env = process.env) {
|
|
18
|
+
if (explicit !== undefined)
|
|
19
|
+
return explicit;
|
|
20
|
+
const raw = env[MEMORY_INFERENCE_PORT_ENV];
|
|
21
|
+
if (raw !== undefined) {
|
|
22
|
+
const parsed = Number.parseInt(raw, 10);
|
|
23
|
+
if (Number.isSafeInteger(parsed) && parsed > 0 && parsed <= 0xffff)
|
|
24
|
+
return parsed;
|
|
25
|
+
throw new Error(`${MEMORY_INFERENCE_PORT_ENV} must be a TCP port number (got ${raw})`);
|
|
26
|
+
}
|
|
27
|
+
return MEMORY_INFERENCE_DEFAULT_PORT;
|
|
28
|
+
}
|
|
29
|
+
export async function connectMemoryInferenceClient(options = {}) {
|
|
30
|
+
const host = options.host ?? MEMORY_INFERENCE_DEFAULT_HOST;
|
|
31
|
+
const port = resolveMemoryInferencePort(options.port);
|
|
32
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? 10_000;
|
|
33
|
+
const connectTimeoutMs = options.connectTimeoutMs ?? 5_000;
|
|
34
|
+
let requestSequence = 0;
|
|
35
|
+
let closed = false;
|
|
36
|
+
let closedResolve;
|
|
37
|
+
const closedPromise = new Promise((resolve) => {
|
|
38
|
+
closedResolve = resolve;
|
|
39
|
+
});
|
|
40
|
+
const pending = new Map();
|
|
41
|
+
let socket;
|
|
42
|
+
const failAll = (message) => {
|
|
43
|
+
for (const [id, entry] of pending) {
|
|
44
|
+
clearTimeout(entry.timer);
|
|
45
|
+
pending.delete(id);
|
|
46
|
+
entry.reject(new Error(message));
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const request = async (kind, fields) => {
|
|
50
|
+
if (closed)
|
|
51
|
+
throw new Error("memory inference client is closed");
|
|
52
|
+
const requestId = ++requestSequence;
|
|
53
|
+
const message = { kind, requestId, ...fields };
|
|
54
|
+
return await new Promise((resolve, reject) => {
|
|
55
|
+
const timer = setTimeout(() => {
|
|
56
|
+
pending.delete(requestId);
|
|
57
|
+
reject(new Error(`memory inference request "${kind}" timed out after ${requestTimeoutMs}ms`));
|
|
58
|
+
}, requestTimeoutMs);
|
|
59
|
+
timer.unref?.();
|
|
60
|
+
pending.set(requestId, {
|
|
61
|
+
resolve: (response) => {
|
|
62
|
+
if (response.kind === "ok") {
|
|
63
|
+
resolve(response.result);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
reject(new Error(`memory inference "${kind}" failed (${response.code}): ${response.message}`));
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
reject,
|
|
70
|
+
timer,
|
|
71
|
+
});
|
|
72
|
+
socket.write(encodeInferenceMessage(message), (error) => {
|
|
73
|
+
// Node's write callback receives null on success (not undefined).
|
|
74
|
+
if (error != null) {
|
|
75
|
+
const entry = pending.get(requestId);
|
|
76
|
+
if (entry !== undefined) {
|
|
77
|
+
clearTimeout(entry.timer);
|
|
78
|
+
pending.delete(requestId);
|
|
79
|
+
entry.reject(error instanceof Error ? error : new Error(String(error)));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
await new Promise((resolve, reject) => {
|
|
86
|
+
socket = connect({ port, host });
|
|
87
|
+
const onConnectError = (error) => {
|
|
88
|
+
clearTimeout(connectTimer);
|
|
89
|
+
reject(new Error(`memory inference server unreachable at ${host}:${port}: ${error.message}`));
|
|
90
|
+
};
|
|
91
|
+
const connectTimer = setTimeout(() => {
|
|
92
|
+
socket.destroy();
|
|
93
|
+
reject(new Error(`memory inference connect to ${host}:${port} timed out after ${connectTimeoutMs}ms`));
|
|
94
|
+
}, connectTimeoutMs);
|
|
95
|
+
connectTimer.unref?.();
|
|
96
|
+
socket.once("error", onConnectError);
|
|
97
|
+
socket.once("connect", () => {
|
|
98
|
+
clearTimeout(connectTimer);
|
|
99
|
+
socket.off("error", onConnectError);
|
|
100
|
+
resolve();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
const decoder = new FrameDecoder();
|
|
104
|
+
socket.on("data", (chunk) => {
|
|
105
|
+
// 帧层异常必须捕获 (复审 P2-1): 超限帧抛 FrameError 即 uncaughtException
|
|
106
|
+
// 会崩掉整个会话进程 — 断连并让 pending 结构化失败。
|
|
107
|
+
let frames;
|
|
108
|
+
try {
|
|
109
|
+
frames = decoder.push(new Uint8Array(chunk));
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
socket.destroy();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
for (const frame of frames) {
|
|
116
|
+
if (frame.byteLength === 0)
|
|
117
|
+
continue;
|
|
118
|
+
try {
|
|
119
|
+
const response = decodeInferenceMessage(frame);
|
|
120
|
+
if (response.kind === "ok" || response.kind === "error") {
|
|
121
|
+
const entry = pending.get(response.requestId);
|
|
122
|
+
if (entry !== undefined) {
|
|
123
|
+
clearTimeout(entry.timer);
|
|
124
|
+
pending.delete(response.requestId);
|
|
125
|
+
entry.resolve(response);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// decode failure: leave the request to time out (bounded by requestTimeoutMs)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
socket.on("error", () => {
|
|
135
|
+
failAll("memory inference connection failed");
|
|
136
|
+
});
|
|
137
|
+
socket.on("close", () => {
|
|
138
|
+
closed = true;
|
|
139
|
+
failAll("memory inference connection closed");
|
|
140
|
+
closedResolve?.();
|
|
141
|
+
});
|
|
142
|
+
// hello 失败路径必须断开 (复审 P2-2): 否则 socket 泄漏且抑制服务端空闲自杀。
|
|
143
|
+
const hello = await request("hello", {
|
|
144
|
+
protocolVersion: MEMORY_INFERENCE_PROTOCOL_VERSION,
|
|
145
|
+
}).catch((error) => {
|
|
146
|
+
socket.destroy();
|
|
147
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
148
|
+
});
|
|
149
|
+
if (hello.protocolVersion !== MEMORY_INFERENCE_PROTOCOL_VERSION) {
|
|
150
|
+
socket.destroy();
|
|
151
|
+
throw new Error(`memory inference protocol version mismatch: client=${MEMORY_INFERENCE_PROTOCOL_VERSION} server=${hello.protocolVersion}`);
|
|
152
|
+
}
|
|
153
|
+
const embedProvider = {
|
|
154
|
+
modelId: hello.embeddingModelId,
|
|
155
|
+
dimensions: hello.dimensions,
|
|
156
|
+
embed: async (texts) => {
|
|
157
|
+
const result = await request("embed", { texts });
|
|
158
|
+
return result.vectors;
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
const reranker = hello.rerankerModelId === null
|
|
162
|
+
? undefined
|
|
163
|
+
: {
|
|
164
|
+
modelId: hello.rerankerModelId,
|
|
165
|
+
rerank: async (query, docs) => {
|
|
166
|
+
const result = await request("rerank", { query, docs });
|
|
167
|
+
return result.scores;
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
const vectorIndex = {
|
|
171
|
+
replicaId: "memory-vector-index",
|
|
172
|
+
upsert: async (entries) => {
|
|
173
|
+
await request("upsert", { entries });
|
|
174
|
+
},
|
|
175
|
+
remove: async (memoryIds) => {
|
|
176
|
+
await request("remove", { memoryIds });
|
|
177
|
+
},
|
|
178
|
+
queryKnn: async (vector, query) => {
|
|
179
|
+
const result = await request("queryKnn", { vector, query });
|
|
180
|
+
return result.hits;
|
|
181
|
+
},
|
|
182
|
+
queryFts: async (queryText, query) => {
|
|
183
|
+
const result = await request("queryFts", { queryText, query });
|
|
184
|
+
return result.hits;
|
|
185
|
+
},
|
|
186
|
+
listMemoryIds: async () => {
|
|
187
|
+
const result = await request("listMemoryIds", {});
|
|
188
|
+
return new Set(result.memoryIds);
|
|
189
|
+
},
|
|
190
|
+
count: async () => {
|
|
191
|
+
const result = await request("count", {});
|
|
192
|
+
return result.count;
|
|
193
|
+
},
|
|
194
|
+
getStoredModelId: async () => {
|
|
195
|
+
const result = await request("getStoredModelId", {});
|
|
196
|
+
// IPC payloads carry null; the local interface speaks undefined.
|
|
197
|
+
return result.modelId ?? undefined;
|
|
198
|
+
},
|
|
199
|
+
close: async () => {
|
|
200
|
+
// 客户端组件的 close 只断开本连接; 服务端索引归服务进程持有。
|
|
201
|
+
socket.destroy();
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
return {
|
|
205
|
+
hello,
|
|
206
|
+
embedProvider,
|
|
207
|
+
reranker,
|
|
208
|
+
vectorIndex,
|
|
209
|
+
close: async () => {
|
|
210
|
+
socket.destroy();
|
|
211
|
+
},
|
|
212
|
+
closed: closedPromise,
|
|
213
|
+
isAlive: () => !closed && !socket.destroyed,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=inference-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inference-client.js","sourceRoot":"","sources":["../../src/memory/inference-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 记忆共享推理服务 — 客户端组件 (记忆共享推理服务设计 §3.3)。\n *\n * 把 IPC 客户端包装成既有 `MemoryVectorComponentsOverrideV1` 形状 (embedProvider\n * / reranker / vectorIndex): 经 `HostCapabilities.memoryStorage.vectorComponents`\n * 注入后, memory capability 的向量操作透明变远程 — 插件契约零改动。会话进程\n * (主会话与 subagent runner) 由此共享服务进程里的一份模型与索引, 不再各自\n * 装配 (裁决 C 根治: 模型一份、LanceDB 单写者)。\n *\n * 失败语义 (设计 §4, 绑定裁决 A「宁漏勿错」): 服务不可达/握手失败/调用超时\n * 抛结构化错误 — 调用方的降级阶梯把它当通道故障处理 (auto-recall 零注入,\n * 显式召回 unavailable); 不回退本地装配 (回退即重开多进程风险)。\n */\nimport { connect, type Socket } from \"node:net\";\nimport { FrameDecoder } from \"@agent-forge/protocol\";\nimport type { MemoryEmbeddingProviderV1 } from \"./embedding-provider.ts\";\nimport type { MemoryRerankerV1 } from \"./embedding-reranker.ts\";\nimport {\n\tdecodeInferenceMessage,\n\tencodeInferenceMessage,\n\tMEMORY_INFERENCE_DEFAULT_HOST,\n\tMEMORY_INFERENCE_DEFAULT_PORT,\n\tMEMORY_INFERENCE_PORT_ENV,\n\tMEMORY_INFERENCE_PROTOCOL_VERSION,\n\ttype MemoryInferenceHelloResultV1,\n\ttype MemoryInferenceRequestV1,\n\ttype MemoryInferenceResponseV1,\n} from \"./inference-protocol.ts\";\nimport type { MemoryVectorHitV1, MemoryVectorIndexQueryV1, MemoryVectorIndexV1 } from \"./vector-index.ts\";\n\nexport interface MemoryInferenceClientOptions {\n\treadonly port?: number;\n\treadonly host?: string;\n\t/** 单请求超时; 缺省 10s (出厂默认值, 登记产品默认值清单)。 */\n\treadonly requestTimeoutMs?: number;\n\treadonly connectTimeoutMs?: number;\n}\n\nexport interface MemoryInferenceClientV1 {\n\treadonly hello: MemoryInferenceHelloResultV1;\n\treadonly embedProvider: MemoryEmbeddingProviderV1;\n\treadonly reranker: MemoryRerankerV1 | undefined;\n\treadonly vectorIndex: MemoryVectorIndexV1;\n\tclose(): Promise<void>;\n\t/** 连接断开 (服务死亡/网络故障) 后 resolve — 调用方可据此触发重拉起。 */\n\treadonly closed: Promise<void>;\n\t/** 连接存活探测 (Slice B2 断连自愈用): socket 已建立且未进入关闭态。 */\n\tisAlive(): boolean;\n}\n\nexport function resolveMemoryInferencePort(explicit?: number, env: NodeJS.ProcessEnv = process.env): number {\n\tif (explicit !== undefined) return explicit;\n\tconst raw = env[MEMORY_INFERENCE_PORT_ENV];\n\tif (raw !== undefined) {\n\t\tconst parsed = Number.parseInt(raw, 10);\n\t\tif (Number.isSafeInteger(parsed) && parsed > 0 && parsed <= 0xffff) return parsed;\n\t\tthrow new Error(`${MEMORY_INFERENCE_PORT_ENV} must be a TCP port number (got ${raw})`);\n\t}\n\treturn MEMORY_INFERENCE_DEFAULT_PORT;\n}\n\nexport async function connectMemoryInferenceClient(\n\toptions: MemoryInferenceClientOptions = {},\n): Promise<MemoryInferenceClientV1> {\n\tconst host = options.host ?? MEMORY_INFERENCE_DEFAULT_HOST;\n\tconst port = resolveMemoryInferencePort(options.port);\n\tconst requestTimeoutMs = options.requestTimeoutMs ?? 10_000;\n\tconst connectTimeoutMs = options.connectTimeoutMs ?? 5_000;\n\n\tlet requestSequence = 0;\n\tlet closed = false;\n\tlet closedResolve: (() => void) | undefined;\n\tconst closedPromise = new Promise<void>((resolve) => {\n\t\tclosedResolve = resolve;\n\t});\n\tinterface Pending {\n\t\tresolve(result: MemoryInferenceResponseV1): void;\n\t\treject(error: Error): void;\n\t\ttimer: NodeJS.Timeout;\n\t}\n\tconst pending = new Map<number, Pending>();\n\tlet socket!: Socket;\n\n\tconst failAll = (message: string): void => {\n\t\tfor (const [id, entry] of pending) {\n\t\t\tclearTimeout(entry.timer);\n\t\t\tpending.delete(id);\n\t\t\tentry.reject(new Error(message));\n\t\t}\n\t};\n\n\tconst request = async <T>(kind: MemoryInferenceRequestV1[\"kind\"], fields: Record<string, unknown>): Promise<T> => {\n\t\tif (closed) throw new Error(\"memory inference client is closed\");\n\t\tconst requestId = ++requestSequence;\n\t\tconst message = { kind, requestId, ...fields } as unknown as MemoryInferenceRequestV1;\n\t\treturn await new Promise<T>((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tpending.delete(requestId);\n\t\t\t\treject(new Error(`memory inference request \"${kind}\" timed out after ${requestTimeoutMs}ms`));\n\t\t\t}, requestTimeoutMs);\n\t\t\ttimer.unref?.();\n\t\t\tpending.set(requestId, {\n\t\t\t\tresolve: (response) => {\n\t\t\t\t\tif (response.kind === \"ok\") {\n\t\t\t\t\t\tresolve(response.result as T);\n\t\t\t\t\t} else {\n\t\t\t\t\t\treject(new Error(`memory inference \"${kind}\" failed (${response.code}): ${response.message}`));\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\treject,\n\t\t\t\ttimer,\n\t\t\t});\n\t\t\tsocket.write(encodeInferenceMessage(message), (error) => {\n\t\t\t\t// Node's write callback receives null on success (not undefined).\n\t\t\t\tif (error != null) {\n\t\t\t\t\tconst entry = pending.get(requestId);\n\t\t\t\t\tif (entry !== undefined) {\n\t\t\t\t\t\tclearTimeout(entry.timer);\n\t\t\t\t\t\tpending.delete(requestId);\n\t\t\t\t\t\tentry.reject(error instanceof Error ? error : new Error(String(error)));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t};\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tsocket = connect({ port, host });\n\t\tconst onConnectError = (error: Error): void => {\n\t\t\tclearTimeout(connectTimer);\n\t\t\treject(new Error(`memory inference server unreachable at ${host}:${port}: ${error.message}`));\n\t\t};\n\t\tconst connectTimer = setTimeout(() => {\n\t\t\tsocket.destroy();\n\t\t\treject(new Error(`memory inference connect to ${host}:${port} timed out after ${connectTimeoutMs}ms`));\n\t\t}, connectTimeoutMs);\n\t\tconnectTimer.unref?.();\n\t\tsocket.once(\"error\", onConnectError);\n\t\tsocket.once(\"connect\", () => {\n\t\t\tclearTimeout(connectTimer);\n\t\t\tsocket.off(\"error\", onConnectError);\n\t\t\tresolve();\n\t\t});\n\t});\n\n\tconst decoder = new FrameDecoder();\n\tsocket.on(\"data\", (chunk: Buffer) => {\n\t\t// 帧层异常必须捕获 (复审 P2-1): 超限帧抛 FrameError 即 uncaughtException\n\t\t// 会崩掉整个会话进程 — 断连并让 pending 结构化失败。\n\t\tlet frames: Uint8Array[];\n\t\ttry {\n\t\t\tframes = decoder.push(new Uint8Array(chunk));\n\t\t} catch {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tfor (const frame of frames) {\n\t\t\tif (frame.byteLength === 0) continue;\n\t\t\ttry {\n\t\t\t\tconst response = decodeInferenceMessage(frame) as MemoryInferenceResponseV1;\n\t\t\t\tif (response.kind === \"ok\" || response.kind === \"error\") {\n\t\t\t\t\tconst entry = pending.get(response.requestId);\n\t\t\t\t\tif (entry !== undefined) {\n\t\t\t\t\t\tclearTimeout(entry.timer);\n\t\t\t\t\t\tpending.delete(response.requestId);\n\t\t\t\t\t\tentry.resolve(response);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// decode failure: leave the request to time out (bounded by requestTimeoutMs)\n\t\t\t}\n\t\t}\n\t});\n\tsocket.on(\"error\", () => {\n\t\tfailAll(\"memory inference connection failed\");\n\t});\n\tsocket.on(\"close\", () => {\n\t\tclosed = true;\n\t\tfailAll(\"memory inference connection closed\");\n\t\tclosedResolve?.();\n\t});\n\n\t// hello 失败路径必须断开 (复审 P2-2): 否则 socket 泄漏且抑制服务端空闲自杀。\n\tconst hello = await request<MemoryInferenceHelloResultV1>(\"hello\", {\n\t\tprotocolVersion: MEMORY_INFERENCE_PROTOCOL_VERSION,\n\t}).catch((error: unknown) => {\n\t\tsocket.destroy();\n\t\tthrow error instanceof Error ? error : new Error(String(error));\n\t});\n\tif (hello.protocolVersion !== MEMORY_INFERENCE_PROTOCOL_VERSION) {\n\t\tsocket.destroy();\n\t\tthrow new Error(\n\t\t\t`memory inference protocol version mismatch: client=${MEMORY_INFERENCE_PROTOCOL_VERSION} server=${hello.protocolVersion}`,\n\t\t);\n\t}\n\n\tconst embedProvider: MemoryEmbeddingProviderV1 = {\n\t\tmodelId: hello.embeddingModelId,\n\t\tdimensions: hello.dimensions,\n\t\tembed: async (texts) => {\n\t\t\tconst result = await request<{ vectors: readonly (readonly number[])[] }>(\"embed\", { texts });\n\t\t\treturn result.vectors;\n\t\t},\n\t};\n\n\tconst reranker: MemoryRerankerV1 | undefined =\n\t\thello.rerankerModelId === null\n\t\t\t? undefined\n\t\t\t: {\n\t\t\t\t\tmodelId: hello.rerankerModelId,\n\t\t\t\t\trerank: async (query, docs) => {\n\t\t\t\t\t\tconst result = await request<{ scores: readonly number[] }>(\"rerank\", { query, docs });\n\t\t\t\t\t\treturn result.scores;\n\t\t\t\t\t},\n\t\t\t\t};\n\n\tconst vectorIndex: MemoryVectorIndexV1 = {\n\t\treplicaId: \"memory-vector-index\",\n\t\tupsert: async (entries) => {\n\t\t\tawait request(\"upsert\", { entries });\n\t\t},\n\t\tremove: async (memoryIds) => {\n\t\t\tawait request(\"remove\", { memoryIds });\n\t\t},\n\t\tqueryKnn: async (vector, query: MemoryVectorIndexQueryV1) => {\n\t\t\tconst result = await request<{ hits: readonly MemoryVectorHitV1[] }>(\"queryKnn\", { vector, query });\n\t\t\treturn result.hits;\n\t\t},\n\t\tqueryFts: async (queryText, query: MemoryVectorIndexQueryV1) => {\n\t\t\tconst result = await request<{ hits: readonly MemoryVectorHitV1[] }>(\"queryFts\", { queryText, query });\n\t\t\treturn result.hits;\n\t\t},\n\t\tlistMemoryIds: async () => {\n\t\t\tconst result = await request<{ memoryIds: readonly string[] }>(\"listMemoryIds\", {});\n\t\t\treturn new Set(result.memoryIds);\n\t\t},\n\t\tcount: async () => {\n\t\t\tconst result = await request<{ count: number }>(\"count\", {});\n\t\t\treturn result.count;\n\t\t},\n\t\tgetStoredModelId: async () => {\n\t\t\tconst result = await request<{ modelId: string | null }>(\"getStoredModelId\", {});\n\t\t\t// IPC payloads carry null; the local interface speaks undefined.\n\t\t\treturn result.modelId ?? undefined;\n\t\t},\n\t\tclose: async () => {\n\t\t\t// 客户端组件的 close 只断开本连接; 服务端索引归服务进程持有。\n\t\t\tsocket.destroy();\n\t\t},\n\t};\n\n\treturn {\n\t\thello,\n\t\tembedProvider,\n\t\treranker,\n\t\tvectorIndex,\n\t\tclose: async () => {\n\t\t\tsocket.destroy();\n\t\t},\n\t\tclosed: closedPromise,\n\t\tisAlive: () => !closed && !socket.destroyed,\n\t};\n}\n"]}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { MemoryVectorHitV1, MemoryVectorIndexEntryV1, MemoryVectorIndexQueryV1 } from "./vector-index.ts";
|
|
2
|
+
/** bump on any breaking message-shape change; self-invocation normally stays same-build. */
|
|
3
|
+
export declare const MEMORY_INFERENCE_PROTOCOL_VERSION = 1;
|
|
4
|
+
/** Loopback 单例端口 (出厂默认值, 已登记 docs/design/产品默认值清单.md)。 */
|
|
5
|
+
export declare const MEMORY_INFERENCE_DEFAULT_PORT = 53187;
|
|
6
|
+
export declare const MEMORY_INFERENCE_PORT_ENV = "AGENT_FORGE_MEMORY_INFERENCE_PORT";
|
|
7
|
+
export declare const MEMORY_INFERENCE_DEFAULT_HOST = "127.0.0.1";
|
|
8
|
+
/** 客户端→服务端请求。requestId 由客户端按连接单调分配, 用于响应路由。 */
|
|
9
|
+
export type MemoryInferenceRequestV1 = {
|
|
10
|
+
readonly kind: "hello";
|
|
11
|
+
readonly requestId: number;
|
|
12
|
+
readonly protocolVersion: number;
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: "embed";
|
|
15
|
+
readonly requestId: number;
|
|
16
|
+
readonly texts: readonly string[];
|
|
17
|
+
} | {
|
|
18
|
+
readonly kind: "rerank";
|
|
19
|
+
readonly requestId: number;
|
|
20
|
+
readonly query: string;
|
|
21
|
+
readonly docs: readonly string[];
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: "queryKnn";
|
|
24
|
+
requestId: number;
|
|
25
|
+
readonly vector: readonly number[];
|
|
26
|
+
readonly query: MemoryVectorIndexQueryV1;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: "queryFts";
|
|
29
|
+
requestId: number;
|
|
30
|
+
readonly queryText: string;
|
|
31
|
+
readonly query: MemoryVectorIndexQueryV1;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: "upsert";
|
|
34
|
+
readonly requestId: number;
|
|
35
|
+
readonly entries: readonly MemoryVectorIndexEntryV1[];
|
|
36
|
+
} | {
|
|
37
|
+
readonly kind: "remove";
|
|
38
|
+
readonly requestId: number;
|
|
39
|
+
readonly memoryIds: readonly string[];
|
|
40
|
+
} | {
|
|
41
|
+
readonly kind: "listMemoryIds";
|
|
42
|
+
readonly requestId: number;
|
|
43
|
+
} | {
|
|
44
|
+
readonly kind: "count";
|
|
45
|
+
readonly requestId: number;
|
|
46
|
+
} | {
|
|
47
|
+
readonly kind: "getStoredModelId";
|
|
48
|
+
readonly requestId: number;
|
|
49
|
+
} | {
|
|
50
|
+
readonly kind: "ping";
|
|
51
|
+
readonly requestId: number;
|
|
52
|
+
};
|
|
53
|
+
/** hello 握手的服务端事实: 模型身份与维度 (客户端组件据此构造 embedProvider 形状)。 */
|
|
54
|
+
export interface MemoryInferenceHelloResultV1 {
|
|
55
|
+
readonly protocolVersion: number;
|
|
56
|
+
readonly embeddingModelId: string;
|
|
57
|
+
readonly dimensions: number;
|
|
58
|
+
readonly rerankerModelId: string | null;
|
|
59
|
+
/**
|
|
60
|
+
* 服务进程的 agent-dir 身份 (2026-10-06 串线修复): 会话装配归一化
|
|
61
|
+
* (path.resolve, win32 再 casefold) 后与本会话 agentDir 比对, 不一致或缺失
|
|
62
|
+
* (旧版本服务进程, 升级过渡期真实存在) 都拒绝装配 — 杜绝同机多 agent-dir
|
|
63
|
+
* 场景下后连者把读写投影进对方 LanceDB 且全程无告警。wire 语义: 服务端未
|
|
64
|
+
* 配置身份时不上报该字段 (旧兼容); 类型面钉当前协议形状。
|
|
65
|
+
*/
|
|
66
|
+
readonly agentDir: string;
|
|
67
|
+
}
|
|
68
|
+
export type MemoryInferenceResultV1 = MemoryInferenceHelloResultV1 | {
|
|
69
|
+
readonly vectors: readonly (readonly number[])[];
|
|
70
|
+
} | {
|
|
71
|
+
readonly scores: readonly number[];
|
|
72
|
+
} | {
|
|
73
|
+
readonly hits: readonly MemoryVectorHitV1[];
|
|
74
|
+
} | {
|
|
75
|
+
readonly memoryIds: readonly string[];
|
|
76
|
+
} | {
|
|
77
|
+
readonly count: number;
|
|
78
|
+
} | {
|
|
79
|
+
readonly modelId: string | null;
|
|
80
|
+
} | {
|
|
81
|
+
readonly pong: true;
|
|
82
|
+
};
|
|
83
|
+
export type MemoryInferenceResponseV1 = {
|
|
84
|
+
readonly kind: "ok";
|
|
85
|
+
readonly requestId: number;
|
|
86
|
+
readonly result: MemoryInferenceResultV1;
|
|
87
|
+
} | {
|
|
88
|
+
readonly kind: "error";
|
|
89
|
+
readonly requestId: number;
|
|
90
|
+
readonly code: "protocol_version" | "bad_request" | "backend_error";
|
|
91
|
+
readonly message: string;
|
|
92
|
+
};
|
|
93
|
+
export declare function encodeInferenceMessage(message: MemoryInferenceRequestV1 | MemoryInferenceResponseV1): Uint8Array;
|
|
94
|
+
export declare function decodeInferenceMessage(bytes: Uint8Array): MemoryInferenceRequestV1 | MemoryInferenceResponseV1;
|
|
95
|
+
//# sourceMappingURL=inference-protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inference-protocol.d.ts","sourceRoot":"","sources":["../../src/memory/inference-protocol.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 记忆共享推理服务 IPC 协议 (记忆共享推理服务设计 §3.2, 2026-10-05 裁决 C「根治」):\n * 常驻 sidecar 服务进程唯一持有 embedding/reranker 模型与 LanceDB 向量索引,\n * 主会话与所有 subagent runner 进程经 TCP loopback 复用它 — 模型一份、索引\n * 单写者 (多进程各载一份模型/各开同一 LanceDB 目录的放大与锁风险就此消除)。\n *\n * 传输: TCP loopback + 4 字节大端长度前缀帧 + CBOR 载荷 (复用 @agent-forge/protocol\n * 通用层, 与 subagent 协议同款设施; 不复用其通道语义 — 那是任务命令面私有协议)。\n *\n * 单例互斥: 固定默认端口绑定成功者即唯一服务实例 (EADDRINUSE 者转客户端);\n * 端口可经 env `AGENT_FORGE_MEMORY_INFERENCE_PORT` 覆盖 (测试与多 agentDir 场景)。\n * 协议版本不匹配 (同机多版本二进制并存的罕见窗口) 在 hello 握手显式拒绝。\n */\nimport { decodeCbor, encodeCbor, encodeFrame } from \"@agent-forge/protocol\";\nimport type { MemoryVectorHitV1, MemoryVectorIndexEntryV1, MemoryVectorIndexQueryV1 } from \"./vector-index.ts\";\n\n/** bump on any breaking message-shape change; self-invocation normally stays same-build. */\nexport const MEMORY_INFERENCE_PROTOCOL_VERSION = 1;\n\n/** Loopback 单例端口 (出厂默认值, 已登记 docs/design/产品默认值清单.md)。 */\nexport const MEMORY_INFERENCE_DEFAULT_PORT = 53187;\n\nexport const MEMORY_INFERENCE_PORT_ENV = \"AGENT_FORGE_MEMORY_INFERENCE_PORT\";\n\nexport const MEMORY_INFERENCE_DEFAULT_HOST = \"127.0.0.1\";\n\n/** 客户端→服务端请求。requestId 由客户端按连接单调分配, 用于响应路由。 */\nexport type MemoryInferenceRequestV1 =\n\t| { readonly kind: \"hello\"; readonly requestId: number; readonly protocolVersion: number }\n\t| { readonly kind: \"embed\"; readonly requestId: number; readonly texts: readonly string[] }\n\t| { readonly kind: \"rerank\"; readonly requestId: number; readonly query: string; readonly docs: readonly string[] }\n\t| {\n\t\t\treadonly kind: \"queryKnn\";\n\t\t\trequestId: number;\n\t\t\treadonly vector: readonly number[];\n\t\t\treadonly query: MemoryVectorIndexQueryV1;\n\t }\n\t| {\n\t\t\treadonly kind: \"queryFts\";\n\t\t\trequestId: number;\n\t\t\treadonly queryText: string;\n\t\t\treadonly query: MemoryVectorIndexQueryV1;\n\t }\n\t| { readonly kind: \"upsert\"; readonly requestId: number; readonly entries: readonly MemoryVectorIndexEntryV1[] }\n\t| { readonly kind: \"remove\"; readonly requestId: number; readonly memoryIds: readonly string[] }\n\t| { readonly kind: \"listMemoryIds\"; readonly requestId: number }\n\t| { readonly kind: \"count\"; readonly requestId: number }\n\t| { readonly kind: \"getStoredModelId\"; readonly requestId: number }\n\t| { readonly kind: \"ping\"; readonly requestId: number };\n\n/** hello 握手的服务端事实: 模型身份与维度 (客户端组件据此构造 embedProvider 形状)。 */\nexport interface MemoryInferenceHelloResultV1 {\n\treadonly protocolVersion: number;\n\treadonly embeddingModelId: string;\n\treadonly dimensions: number;\n\treadonly rerankerModelId: string | null;\n\t/**\n\t * 服务进程的 agent-dir 身份 (2026-10-06 串线修复): 会话装配归一化\n\t * (path.resolve, win32 再 casefold) 后与本会话 agentDir 比对, 不一致或缺失\n\t * (旧版本服务进程, 升级过渡期真实存在) 都拒绝装配 — 杜绝同机多 agent-dir\n\t * 场景下后连者把读写投影进对方 LanceDB 且全程无告警。wire 语义: 服务端未\n\t * 配置身份时不上报该字段 (旧兼容); 类型面钉当前协议形状。\n\t */\n\treadonly agentDir: string;\n}\n\nexport type MemoryInferenceResultV1 =\n\t| MemoryInferenceHelloResultV1\n\t| { readonly vectors: readonly (readonly number[])[] }\n\t| { readonly scores: readonly number[] }\n\t| { readonly hits: readonly MemoryVectorHitV1[] }\n\t| { readonly memoryIds: readonly string[] }\n\t| { readonly count: number }\n\t| { readonly modelId: string | null }\n\t| { readonly pong: true };\n\nexport type MemoryInferenceResponseV1 =\n\t| { readonly kind: \"ok\"; readonly requestId: number; readonly result: MemoryInferenceResultV1 }\n\t| {\n\t\t\treadonly kind: \"error\";\n\t\t\treadonly requestId: number;\n\t\t\treadonly code: \"protocol_version\" | \"bad_request\" | \"backend_error\";\n\t\t\treadonly message: string;\n\t };\n\nexport function encodeInferenceMessage(message: MemoryInferenceRequestV1 | MemoryInferenceResponseV1): Uint8Array {\n\treturn encodeFrame(encodeCbor(message));\n}\n\nexport function decodeInferenceMessage(bytes: Uint8Array): MemoryInferenceRequestV1 | MemoryInferenceResponseV1 {\n\treturn decodeCbor(bytes) as MemoryInferenceRequestV1 | MemoryInferenceResponseV1;\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 记忆共享推理服务 IPC 协议 (记忆共享推理服务设计 §3.2, 2026-10-05 裁决 C「根治」):
|
|
3
|
+
* 常驻 sidecar 服务进程唯一持有 embedding/reranker 模型与 LanceDB 向量索引,
|
|
4
|
+
* 主会话与所有 subagent runner 进程经 TCP loopback 复用它 — 模型一份、索引
|
|
5
|
+
* 单写者 (多进程各载一份模型/各开同一 LanceDB 目录的放大与锁风险就此消除)。
|
|
6
|
+
*
|
|
7
|
+
* 传输: TCP loopback + 4 字节大端长度前缀帧 + CBOR 载荷 (复用 @agent-forge/protocol
|
|
8
|
+
* 通用层, 与 subagent 协议同款设施; 不复用其通道语义 — 那是任务命令面私有协议)。
|
|
9
|
+
*
|
|
10
|
+
* 单例互斥: 固定默认端口绑定成功者即唯一服务实例 (EADDRINUSE 者转客户端);
|
|
11
|
+
* 端口可经 env `AGENT_FORGE_MEMORY_INFERENCE_PORT` 覆盖 (测试与多 agentDir 场景)。
|
|
12
|
+
* 协议版本不匹配 (同机多版本二进制并存的罕见窗口) 在 hello 握手显式拒绝。
|
|
13
|
+
*/
|
|
14
|
+
import { decodeCbor, encodeCbor, encodeFrame } from "@agent-forge/protocol";
|
|
15
|
+
/** bump on any breaking message-shape change; self-invocation normally stays same-build. */
|
|
16
|
+
export const MEMORY_INFERENCE_PROTOCOL_VERSION = 1;
|
|
17
|
+
/** Loopback 单例端口 (出厂默认值, 已登记 docs/design/产品默认值清单.md)。 */
|
|
18
|
+
export const MEMORY_INFERENCE_DEFAULT_PORT = 53187;
|
|
19
|
+
export const MEMORY_INFERENCE_PORT_ENV = "AGENT_FORGE_MEMORY_INFERENCE_PORT";
|
|
20
|
+
export const MEMORY_INFERENCE_DEFAULT_HOST = "127.0.0.1";
|
|
21
|
+
export function encodeInferenceMessage(message) {
|
|
22
|
+
return encodeFrame(encodeCbor(message));
|
|
23
|
+
}
|
|
24
|
+
export function decodeInferenceMessage(bytes) {
|
|
25
|
+
return decodeCbor(bytes);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=inference-protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inference-protocol.js","sourceRoot":"","sources":["../../src/memory/inference-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAG5E,4FAA4F;AAC5F,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,CAAC;AAEnD,iGAAyD;AACzD,MAAM,CAAC,MAAM,6BAA6B,GAAG,KAAK,CAAC;AAEnD,MAAM,CAAC,MAAM,yBAAyB,GAAG,mCAAmC,CAAC;AAE7E,MAAM,CAAC,MAAM,6BAA6B,GAAG,WAAW,CAAC;AA6DzD,MAAM,UAAU,sBAAsB,CAAC,OAA6D,EAAc;IACjH,OAAO,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;AAAA,CACxC;AAED,MAAM,UAAU,sBAAsB,CAAC,KAAiB,EAAwD;IAC/G,OAAO,UAAU,CAAC,KAAK,CAAyD,CAAC;AAAA,CACjF","sourcesContent":["/**\n * 记忆共享推理服务 IPC 协议 (记忆共享推理服务设计 §3.2, 2026-10-05 裁决 C「根治」):\n * 常驻 sidecar 服务进程唯一持有 embedding/reranker 模型与 LanceDB 向量索引,\n * 主会话与所有 subagent runner 进程经 TCP loopback 复用它 — 模型一份、索引\n * 单写者 (多进程各载一份模型/各开同一 LanceDB 目录的放大与锁风险就此消除)。\n *\n * 传输: TCP loopback + 4 字节大端长度前缀帧 + CBOR 载荷 (复用 @agent-forge/protocol\n * 通用层, 与 subagent 协议同款设施; 不复用其通道语义 — 那是任务命令面私有协议)。\n *\n * 单例互斥: 固定默认端口绑定成功者即唯一服务实例 (EADDRINUSE 者转客户端);\n * 端口可经 env `AGENT_FORGE_MEMORY_INFERENCE_PORT` 覆盖 (测试与多 agentDir 场景)。\n * 协议版本不匹配 (同机多版本二进制并存的罕见窗口) 在 hello 握手显式拒绝。\n */\nimport { decodeCbor, encodeCbor, encodeFrame } from \"@agent-forge/protocol\";\nimport type { MemoryVectorHitV1, MemoryVectorIndexEntryV1, MemoryVectorIndexQueryV1 } from \"./vector-index.ts\";\n\n/** bump on any breaking message-shape change; self-invocation normally stays same-build. */\nexport const MEMORY_INFERENCE_PROTOCOL_VERSION = 1;\n\n/** Loopback 单例端口 (出厂默认值, 已登记 docs/design/产品默认值清单.md)。 */\nexport const MEMORY_INFERENCE_DEFAULT_PORT = 53187;\n\nexport const MEMORY_INFERENCE_PORT_ENV = \"AGENT_FORGE_MEMORY_INFERENCE_PORT\";\n\nexport const MEMORY_INFERENCE_DEFAULT_HOST = \"127.0.0.1\";\n\n/** 客户端→服务端请求。requestId 由客户端按连接单调分配, 用于响应路由。 */\nexport type MemoryInferenceRequestV1 =\n\t| { readonly kind: \"hello\"; readonly requestId: number; readonly protocolVersion: number }\n\t| { readonly kind: \"embed\"; readonly requestId: number; readonly texts: readonly string[] }\n\t| { readonly kind: \"rerank\"; readonly requestId: number; readonly query: string; readonly docs: readonly string[] }\n\t| {\n\t\t\treadonly kind: \"queryKnn\";\n\t\t\trequestId: number;\n\t\t\treadonly vector: readonly number[];\n\t\t\treadonly query: MemoryVectorIndexQueryV1;\n\t }\n\t| {\n\t\t\treadonly kind: \"queryFts\";\n\t\t\trequestId: number;\n\t\t\treadonly queryText: string;\n\t\t\treadonly query: MemoryVectorIndexQueryV1;\n\t }\n\t| { readonly kind: \"upsert\"; readonly requestId: number; readonly entries: readonly MemoryVectorIndexEntryV1[] }\n\t| { readonly kind: \"remove\"; readonly requestId: number; readonly memoryIds: readonly string[] }\n\t| { readonly kind: \"listMemoryIds\"; readonly requestId: number }\n\t| { readonly kind: \"count\"; readonly requestId: number }\n\t| { readonly kind: \"getStoredModelId\"; readonly requestId: number }\n\t| { readonly kind: \"ping\"; readonly requestId: number };\n\n/** hello 握手的服务端事实: 模型身份与维度 (客户端组件据此构造 embedProvider 形状)。 */\nexport interface MemoryInferenceHelloResultV1 {\n\treadonly protocolVersion: number;\n\treadonly embeddingModelId: string;\n\treadonly dimensions: number;\n\treadonly rerankerModelId: string | null;\n\t/**\n\t * 服务进程的 agent-dir 身份 (2026-10-06 串线修复): 会话装配归一化\n\t * (path.resolve, win32 再 casefold) 后与本会话 agentDir 比对, 不一致或缺失\n\t * (旧版本服务进程, 升级过渡期真实存在) 都拒绝装配 — 杜绝同机多 agent-dir\n\t * 场景下后连者把读写投影进对方 LanceDB 且全程无告警。wire 语义: 服务端未\n\t * 配置身份时不上报该字段 (旧兼容); 类型面钉当前协议形状。\n\t */\n\treadonly agentDir: string;\n}\n\nexport type MemoryInferenceResultV1 =\n\t| MemoryInferenceHelloResultV1\n\t| { readonly vectors: readonly (readonly number[])[] }\n\t| { readonly scores: readonly number[] }\n\t| { readonly hits: readonly MemoryVectorHitV1[] }\n\t| { readonly memoryIds: readonly string[] }\n\t| { readonly count: number }\n\t| { readonly modelId: string | null }\n\t| { readonly pong: true };\n\nexport type MemoryInferenceResponseV1 =\n\t| { readonly kind: \"ok\"; readonly requestId: number; readonly result: MemoryInferenceResultV1 }\n\t| {\n\t\t\treadonly kind: \"error\";\n\t\t\treadonly requestId: number;\n\t\t\treadonly code: \"protocol_version\" | \"bad_request\" | \"backend_error\";\n\t\t\treadonly message: string;\n\t };\n\nexport function encodeInferenceMessage(message: MemoryInferenceRequestV1 | MemoryInferenceResponseV1): Uint8Array {\n\treturn encodeFrame(encodeCbor(message));\n}\n\nexport function decodeInferenceMessage(bytes: Uint8Array): MemoryInferenceRequestV1 | MemoryInferenceResponseV1 {\n\treturn decodeCbor(bytes) as MemoryInferenceRequestV1 | MemoryInferenceResponseV1;\n}\n"]}
|