@x12i/memorix-scoper-service 0.0.0-stage → 3.1.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 +29 -2
- package/dist/caller.d.ts +26 -0
- package/dist/caller.d.ts.map +1 -0
- package/dist/caller.js +61 -0
- package/dist/caller.js.map +1 -0
- package/dist/client-api.d.ts +69 -0
- package/dist/client-api.d.ts.map +1 -0
- package/dist/client-api.js +202 -0
- package/dist/client-api.js.map +1 -0
- package/dist/create-service.d.ts +146 -0
- package/dist/create-service.d.ts.map +1 -0
- package/dist/create-service.js +157 -0
- package/dist/create-service.js.map +1 -0
- package/dist/http.d.ts +14 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +47 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/load-env.d.ts +12 -0
- package/dist/load-env.d.ts.map +1 -0
- package/dist/load-env.js +25 -0
- package/dist/load-env.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +79 -0
- package/dist/main.js.map +1 -0
- package/dist/mcp-http.d.ts +22 -0
- package/dist/mcp-http.d.ts.map +1 -0
- package/dist/mcp-http.js +74 -0
- package/dist/mcp-http.js.map +1 -0
- package/dist/scoper-host.d.ts +27 -0
- package/dist/scoper-host.d.ts.map +1 -0
- package/dist/scoper-host.js +118 -0
- package/dist/scoper-host.js.map +1 -0
- package/dist/versions.d.ts +6 -0
- package/dist/versions.d.ts.map +1 -0
- package/dist/versions.js +27 -0
- package/dist/versions.js.map +1 -0
- package/package.json +50 -3
package/README.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @x12i/memorix-scoper-service
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Scoper over HTTP. Reads Memorix through the Memorix client API as the caller, and writes `_markdown` to the Memorix service.
|
|
4
|
+
|
|
5
|
+
## Run it alone
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
MEMORIX_SERVICE_URL=http://127.0.0.1:5100 npx memorix-scoper-service
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Listens on `127.0.0.1:8787` (`HOST`, `PORT`). Uses `MEMORIX_CLIENT_API_URL` when set, otherwise the client API on port 5120, otherwise starts the optional `@x12i/memorix-client-api`. `SCOPER_SERVICE_ENV_FILE` names an env file to load into unset variables.
|
|
12
|
+
|
|
13
|
+
## Mount it inside the client API
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createScoperService, isScoperRoute } from "@x12i/memorix-scoper-service";
|
|
17
|
+
|
|
18
|
+
const scoper = createScoperService({
|
|
19
|
+
env: {
|
|
20
|
+
MEMORIX_CLIENT_API_URL: "http://127.0.0.1:5120",
|
|
21
|
+
MEMORIX_SERVICE_URL: process.env.MEMORIX_SERVICE_URL,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (isScoperRoute(req.method, req.url ?? "/")) return scoper.handler(req, res);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`createScoperService` does not open a port. The host serves `GET /api/v1/scoper`, `POST /api/v1/scoper/scope`, `POST /api/v1/scoper/subject-data`, and `GET|POST /api/v1/mcp/scoper` on its own port, and keeps its own `GET /health`. Callers send `x-memorix-org-id`, `x-memorix-agent-ids`, `x-memorix-caller`, and their `authorization`; Scoper sends that `authorization` on every read.
|
|
29
|
+
|
|
30
|
+
Full reference: [operations](https://github.com/x12i/scoper-monorepo/blob/main/docs/operations.md).
|
package/dist/caller.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { IncomingMessage } from "node:http";
|
|
2
|
+
import type { TrustedExecutionContext } from "@x12i/memorix-scoper-contracts";
|
|
3
|
+
/** Who is calling, from the headers the Memorix client API forwards. Never from the body. */
|
|
4
|
+
export interface CallerCall {
|
|
5
|
+
context: TrustedExecutionContext;
|
|
6
|
+
/** The caller's own `authorization` header. Every read is sent with it. */
|
|
7
|
+
authorization?: string;
|
|
8
|
+
}
|
|
9
|
+
export type CallerResult = {
|
|
10
|
+
ok: true;
|
|
11
|
+
call: CallerCall;
|
|
12
|
+
} | {
|
|
13
|
+
ok: false;
|
|
14
|
+
code: "SCOPE_INVALID";
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function callerFromHeaders(req: IncomingMessage): CallerResult;
|
|
18
|
+
export declare function stripUntrusted(body: unknown): unknown;
|
|
19
|
+
/**
|
|
20
|
+
* Run one request with the caller's token in scope. The token stays out of the
|
|
21
|
+
* execution context, so it never reaches traces, evidence, or a model prompt.
|
|
22
|
+
*/
|
|
23
|
+
export declare function withCaller<T>(call: CallerCall, run: () => T): T;
|
|
24
|
+
/** Headers for reads made inside `withCaller`. Empty outside a request. */
|
|
25
|
+
export declare function forwardedHeaders(): Record<string, string>;
|
|
26
|
+
//# sourceMappingURL=caller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caller.d.ts","sourceRoot":"","sources":["../src/caller.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE9E,6FAA6F;AAC7F,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,uBAAuB,CAAC;IACjC,2EAA2E;IAC3E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAC9B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAU1D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,eAAe,GAAG,YAAY,CA4BpE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAOrD;AAID;;;GAGG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAE/D;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGzD"}
|
package/dist/caller.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
const UNTRUSTED = ["orgId", "agentIds", "caller", "token", "credentials", "memorixBaseUrl"];
|
|
4
|
+
function headerValue(value) {
|
|
5
|
+
const raw = Array.isArray(value) ? value[0] : value;
|
|
6
|
+
const trimmed = raw?.trim();
|
|
7
|
+
return trimmed || undefined;
|
|
8
|
+
}
|
|
9
|
+
export function callerFromHeaders(req) {
|
|
10
|
+
const orgId = headerValue(req.headers["x-memorix-org-id"]);
|
|
11
|
+
const agentIds = (headerValue(req.headers["x-memorix-agent-ids"]) ?? "")
|
|
12
|
+
.split(",")
|
|
13
|
+
.map((id) => id.trim())
|
|
14
|
+
.filter(Boolean);
|
|
15
|
+
const caller = headerValue(req.headers["x-memorix-caller"]);
|
|
16
|
+
const missing = [
|
|
17
|
+
...(orgId ? [] : ["x-memorix-org-id"]),
|
|
18
|
+
...(agentIds.length ? [] : ["x-memorix-agent-ids"]),
|
|
19
|
+
...(caller ? [] : ["x-memorix-caller"]),
|
|
20
|
+
];
|
|
21
|
+
if (!orgId || !agentIds.length || !caller) {
|
|
22
|
+
return { ok: false, code: "SCOPE_INVALID", message: `missing header: ${missing.join(", ")}` };
|
|
23
|
+
}
|
|
24
|
+
const authorization = headerValue(req.headers.authorization);
|
|
25
|
+
return {
|
|
26
|
+
ok: true,
|
|
27
|
+
call: {
|
|
28
|
+
context: {
|
|
29
|
+
orgId,
|
|
30
|
+
agentIds,
|
|
31
|
+
caller,
|
|
32
|
+
traceId: headerValue(req.headers["x-trace-id"]) ?? randomUUID(),
|
|
33
|
+
},
|
|
34
|
+
...(authorization ? { authorization } : {}),
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function stripUntrusted(body) {
|
|
39
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
40
|
+
return body;
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const [key, value] of Object.entries(body)) {
|
|
43
|
+
if (!UNTRUSTED.includes(key))
|
|
44
|
+
out[key] = value;
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
const forwarded = new AsyncLocalStorage();
|
|
49
|
+
/**
|
|
50
|
+
* Run one request with the caller's token in scope. The token stays out of the
|
|
51
|
+
* execution context, so it never reaches traces, evidence, or a model prompt.
|
|
52
|
+
*/
|
|
53
|
+
export function withCaller(call, run) {
|
|
54
|
+
return forwarded.run({ authorization: call.authorization }, run);
|
|
55
|
+
}
|
|
56
|
+
/** Headers for reads made inside `withCaller`. Empty outside a request. */
|
|
57
|
+
export function forwardedHeaders() {
|
|
58
|
+
const authorization = forwarded.getStore()?.authorization;
|
|
59
|
+
return authorization ? { authorization } : {};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=caller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caller.js","sourceRoot":"","sources":["../src/caller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** Default Memorix client API. Same host, port 5120. */
|
|
2
|
+
export declare const DEFAULT_CLIENT_API_URL = "http://127.0.0.1:5120";
|
|
3
|
+
/** `GET /health` reports this `service` value. */
|
|
4
|
+
export declare const CLIENT_API_SERVICE_NAME = "@x12i/memorix-client-api";
|
|
5
|
+
export type ClientApiLocation = {
|
|
6
|
+
kind: "configured" | "discovered";
|
|
7
|
+
url: string;
|
|
8
|
+
} | {
|
|
9
|
+
kind: "start";
|
|
10
|
+
url: string;
|
|
11
|
+
port: number;
|
|
12
|
+
};
|
|
13
|
+
export interface ClientApiProcess {
|
|
14
|
+
killed: boolean;
|
|
15
|
+
kill(signal?: NodeJS.Signals | number): boolean;
|
|
16
|
+
once(event: "exit", listener: (code: number | null) => void): unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface DiscoverClientApiOptions {
|
|
19
|
+
env?: NodeJS.ProcessEnv;
|
|
20
|
+
/** Probed when `MEMORIX_CLIENT_API_URL` is unset. Default `http://127.0.0.1:5120`. */
|
|
21
|
+
defaultUrl?: string;
|
|
22
|
+
fetchImpl?: typeof fetch;
|
|
23
|
+
}
|
|
24
|
+
export interface SpawnClientApiOptions {
|
|
25
|
+
port: number;
|
|
26
|
+
/** Scoper base URL the API probes and then forwards Scoper routes to. */
|
|
27
|
+
scoperUrl: string;
|
|
28
|
+
memorixServiceUrl: string;
|
|
29
|
+
env?: NodeJS.ProcessEnv;
|
|
30
|
+
/** Test hook. Default is the installed package's `dist/cli.js`. */
|
|
31
|
+
cliPath?: string;
|
|
32
|
+
spawnImpl?: (command: string, args: readonly string[], options: {
|
|
33
|
+
env: NodeJS.ProcessEnv;
|
|
34
|
+
stdio: "inherit";
|
|
35
|
+
windowsHide: true;
|
|
36
|
+
cwd: string;
|
|
37
|
+
}) => ClientApiProcess;
|
|
38
|
+
}
|
|
39
|
+
/** Trimmed http(s) URL without a trailing slash. */
|
|
40
|
+
export declare function httpBaseUrl(raw: string | undefined, name: string): string;
|
|
41
|
+
export declare function optionalHttpBaseUrl(raw: string | undefined, name: string): string | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Where reads go.
|
|
44
|
+
* `MEMORIX_CLIENT_API_URL` wins.
|
|
45
|
+
* Otherwise the client API already listening on port 5120.
|
|
46
|
+
* Otherwise a URL this process can start.
|
|
47
|
+
*/
|
|
48
|
+
export declare function discoverClientApi(options?: DiscoverClientApiOptions): Promise<ClientApiLocation>;
|
|
49
|
+
/** Poll `/health` until it is this client API, or the process has exited. */
|
|
50
|
+
export declare function waitForClientApi(url: string, options?: {
|
|
51
|
+
fetchImpl?: typeof fetch;
|
|
52
|
+
timeoutMs?: number;
|
|
53
|
+
intervalMs?: number;
|
|
54
|
+
exited?: {
|
|
55
|
+
done: boolean;
|
|
56
|
+
code: number | null;
|
|
57
|
+
};
|
|
58
|
+
}): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* `dist/cli.js` beside the package entry. The package has no bin field.
|
|
61
|
+
* `exports` is import-only, so resolution tries `import.meta.resolve` and then walks `node_modules`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function clientApiCliPath(): string;
|
|
64
|
+
/**
|
|
65
|
+
* Start the installed client API.
|
|
66
|
+
* Scoper must already be listening: the API probes `SCOPER_URL` `/health` before it accepts calls.
|
|
67
|
+
*/
|
|
68
|
+
export declare function spawnClientApi(options: SpawnClientApiOptions): ClientApiProcess;
|
|
69
|
+
//# sourceMappingURL=client-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-api.d.ts","sourceRoot":"","sources":["../src/client-api.ts"],"names":[],"mappings":"AAMA,wDAAwD;AACxD,eAAO,MAAM,sBAAsB,0BAA0B,CAAC;AAE9D,kDAAkD;AAClD,eAAO,MAAM,uBAAuB,6BAA6B,CAAC;AAIlE,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,YAAY,GAAG,YAAY,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IAChD,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC;CACvE;AAED,MAAM,WAAW,wBAAwB;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,CACV,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;QAAC,KAAK,EAAE,SAAS,CAAC;QAAC,WAAW,EAAE,IAAI,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,KAClF,gBAAgB,CAAC;CACvB;AAUD,oDAAoD;AACpD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAazE;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG7F;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAqB1G;AAED,6EAA6E;AAC7E,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IACP,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CAC5C,GACL,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAmBzC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,gBAAgB,CAiB/E"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
/** Default Memorix client API. Same host, port 5120. */
|
|
7
|
+
export const DEFAULT_CLIENT_API_URL = "http://127.0.0.1:5120";
|
|
8
|
+
/** `GET /health` reports this `service` value. */
|
|
9
|
+
export const CLIENT_API_SERVICE_NAME = "@x12i/memorix-client-api";
|
|
10
|
+
const PROBE_TIMEOUT_MS = 1500;
|
|
11
|
+
/** Trimmed http(s) URL without a trailing slash. */
|
|
12
|
+
export function httpBaseUrl(raw, name) {
|
|
13
|
+
const trimmed = raw?.trim() ?? "";
|
|
14
|
+
if (!trimmed)
|
|
15
|
+
throw new Error(`${name} is required`);
|
|
16
|
+
let url;
|
|
17
|
+
try {
|
|
18
|
+
url = new URL(trimmed);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new Error(`${name} is not a URL: ${trimmed}`);
|
|
22
|
+
}
|
|
23
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
24
|
+
throw new Error(`${name} must be http or https: ${trimmed}`);
|
|
25
|
+
}
|
|
26
|
+
return trimmed.replace(/\/+$/, "");
|
|
27
|
+
}
|
|
28
|
+
export function optionalHttpBaseUrl(raw, name) {
|
|
29
|
+
if (!raw?.trim())
|
|
30
|
+
return undefined;
|
|
31
|
+
return httpBaseUrl(raw, name);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Where reads go.
|
|
35
|
+
* `MEMORIX_CLIENT_API_URL` wins.
|
|
36
|
+
* Otherwise the client API already listening on port 5120.
|
|
37
|
+
* Otherwise a URL this process can start.
|
|
38
|
+
*/
|
|
39
|
+
export async function discoverClientApi(options = {}) {
|
|
40
|
+
const env = options.env ?? process.env;
|
|
41
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
42
|
+
const explicit = optionalHttpBaseUrl(env.MEMORIX_CLIENT_API_URL, "MEMORIX_CLIENT_API_URL");
|
|
43
|
+
if (explicit) {
|
|
44
|
+
const probe = await probeClientApi(explicit, fetchImpl);
|
|
45
|
+
if (probe.mismatch) {
|
|
46
|
+
throw new Error(`MEMORIX_CLIENT_API_URL ${explicit} answered, and it is not ${CLIENT_API_SERVICE_NAME} (${probe.reason})`);
|
|
47
|
+
}
|
|
48
|
+
return { kind: "configured", url: explicit };
|
|
49
|
+
}
|
|
50
|
+
const fallback = options.defaultUrl?.replace(/\/+$/, "") || DEFAULT_CLIENT_API_URL;
|
|
51
|
+
const probe = await probeClientApi(fallback, fetchImpl);
|
|
52
|
+
if (probe.ok)
|
|
53
|
+
return { kind: "discovered", url: probe.url };
|
|
54
|
+
const preferred = portOf(probe.url);
|
|
55
|
+
const port = await choosePort(preferred);
|
|
56
|
+
return { kind: "start", port, url: `http://127.0.0.1:${port}` };
|
|
57
|
+
}
|
|
58
|
+
/** Poll `/health` until it is this client API, or the process has exited. */
|
|
59
|
+
export async function waitForClientApi(url, options = {}) {
|
|
60
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
61
|
+
const timeoutMs = options.timeoutMs ?? 90_000;
|
|
62
|
+
const intervalMs = options.intervalMs ?? 250;
|
|
63
|
+
const deadline = Date.now() + timeoutMs;
|
|
64
|
+
let reason = "health did not answer";
|
|
65
|
+
while (Date.now() < deadline) {
|
|
66
|
+
if (options.exited?.done) {
|
|
67
|
+
throw new Error(`The Memorix client API process exited (${options.exited.code ?? "signal"}) before ${url} was ready`);
|
|
68
|
+
}
|
|
69
|
+
const probe = await probeClientApi(url, fetchImpl);
|
|
70
|
+
if (probe.ok)
|
|
71
|
+
return;
|
|
72
|
+
reason = probe.reason;
|
|
73
|
+
await sleep(intervalMs);
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`Memorix client API at ${url} did not become ready within ${timeoutMs}ms (${reason})`);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* `dist/cli.js` beside the package entry. The package has no bin field.
|
|
79
|
+
* `exports` is import-only, so resolution tries `import.meta.resolve` and then walks `node_modules`.
|
|
80
|
+
*/
|
|
81
|
+
export function clientApiCliPath() {
|
|
82
|
+
try {
|
|
83
|
+
const entry = fileURLToPath(import.meta.resolve("@x12i/memorix-client-api"));
|
|
84
|
+
const beside = join(dirname(entry), "cli.js");
|
|
85
|
+
if (existsSync(beside))
|
|
86
|
+
return beside;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Some loaders cannot resolve this package's import-only exports.
|
|
90
|
+
}
|
|
91
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
92
|
+
for (let i = 0; i < 6; i++) {
|
|
93
|
+
const cli = join(dir, "node_modules", "@x12i", "memorix-client-api", "dist", "cli.js");
|
|
94
|
+
if (existsSync(cli))
|
|
95
|
+
return cli;
|
|
96
|
+
const parent = dirname(dir);
|
|
97
|
+
if (parent === dir)
|
|
98
|
+
break;
|
|
99
|
+
dir = parent;
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`@x12i/memorix-client-api is not installed, so Scoper cannot start the client API. Set MEMORIX_CLIENT_API_URL or install the package.`);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Start the installed client API.
|
|
105
|
+
* Scoper must already be listening: the API probes `SCOPER_URL` `/health` before it accepts calls.
|
|
106
|
+
*/
|
|
107
|
+
export function spawnClientApi(options) {
|
|
108
|
+
const cli = options.cliPath ?? clientApiCliPath();
|
|
109
|
+
const env = {
|
|
110
|
+
...options.env,
|
|
111
|
+
HOST: "127.0.0.1",
|
|
112
|
+
MEMORIX_SERVICE_URL: options.memorixServiceUrl,
|
|
113
|
+
MEMORIX_CLIENT_API_PORT: String(options.port),
|
|
114
|
+
SCOPER_MODE: "external",
|
|
115
|
+
SCOPER_URL: options.scoperUrl,
|
|
116
|
+
};
|
|
117
|
+
const spawnImpl = options.spawnImpl ?? defaultSpawn;
|
|
118
|
+
return spawnImpl(process.execPath, [cli], {
|
|
119
|
+
env,
|
|
120
|
+
stdio: "inherit",
|
|
121
|
+
windowsHide: true,
|
|
122
|
+
cwd: process.cwd(),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function defaultSpawn(command, args, options) {
|
|
126
|
+
return spawn(command, args, options);
|
|
127
|
+
}
|
|
128
|
+
async function probeClientApi(url, fetchImpl) {
|
|
129
|
+
const base = url.replace(/\/+$/, "");
|
|
130
|
+
try {
|
|
131
|
+
const response = await fetchImpl(`${base}/health`, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
|
|
132
|
+
const text = await response.text();
|
|
133
|
+
let body = null;
|
|
134
|
+
if (text) {
|
|
135
|
+
try {
|
|
136
|
+
body = JSON.parse(text);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
body = null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (response.ok && isClientApiHealth(body)) {
|
|
143
|
+
return { ok: true, mismatch: false, reason: "", url: base };
|
|
144
|
+
}
|
|
145
|
+
const service = record(body)?.service;
|
|
146
|
+
const named = typeof service === "string" ? service : "";
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
mismatch: response.ok && body !== null && !isClientApiHealth(body),
|
|
150
|
+
reason: named ? `service is ${named}` : `health HTTP ${response.status}`,
|
|
151
|
+
url: base,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
return {
|
|
156
|
+
ok: false,
|
|
157
|
+
mismatch: false,
|
|
158
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
159
|
+
url: base,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function isClientApiHealth(body) {
|
|
164
|
+
const health = record(body);
|
|
165
|
+
return health?.ok === true && health.service === CLIENT_API_SERVICE_NAME;
|
|
166
|
+
}
|
|
167
|
+
function record(value) {
|
|
168
|
+
if (value && typeof value === "object" && !Array.isArray(value))
|
|
169
|
+
return value;
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
function portOf(url) {
|
|
173
|
+
const parsed = new URL(url);
|
|
174
|
+
if (parsed.port)
|
|
175
|
+
return Number(parsed.port);
|
|
176
|
+
return parsed.protocol === "https:" ? 443 : 80;
|
|
177
|
+
}
|
|
178
|
+
/** Prefer `preferred` when it is free. Otherwise any free localhost port. */
|
|
179
|
+
async function choosePort(preferred) {
|
|
180
|
+
const same = await listenPort(preferred);
|
|
181
|
+
if (same)
|
|
182
|
+
return same;
|
|
183
|
+
const any = await listenPort(0);
|
|
184
|
+
if (!any)
|
|
185
|
+
throw new Error("No free TCP port for the Memorix client API");
|
|
186
|
+
return any;
|
|
187
|
+
}
|
|
188
|
+
function listenPort(port) {
|
|
189
|
+
return new Promise((resolve) => {
|
|
190
|
+
const server = createServer();
|
|
191
|
+
server.once("error", () => resolve(undefined));
|
|
192
|
+
server.listen(port, "127.0.0.1", () => {
|
|
193
|
+
const addr = server.address();
|
|
194
|
+
const chosen = addr && typeof addr === "object" ? addr.port : undefined;
|
|
195
|
+
server.close(() => resolve(chosen));
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
function sleep(ms) {
|
|
200
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=client-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-api.js","sourceRoot":"","sources":["../src/client-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
2
|
+
import type { CreateScoperOptions } from "@x12i/memorix-scoper";
|
|
3
|
+
import { type ServiceEnv } from "./scoper-host.js";
|
|
4
|
+
export { resolveReasonerKind, readServiceEnv, type ServiceEnv } from "./scoper-host.js";
|
|
5
|
+
declare const SCOPER_PACKAGE = "@x12i/memorix-scoper";
|
|
6
|
+
export interface ServiceOptions {
|
|
7
|
+
port?: number;
|
|
8
|
+
host?: string;
|
|
9
|
+
/** Read for the client API URL, the Memorix URL, the reasoner, and saved functions. Default `process.env`. */
|
|
10
|
+
env?: NodeJS.ProcessEnv;
|
|
11
|
+
/** Merged over the Scoper options built from env. */
|
|
12
|
+
scoperOptions?: Partial<CreateScoperOptions>;
|
|
13
|
+
/** Used for reads and the Memorix write. Default global `fetch`. */
|
|
14
|
+
fetchImpl?: typeof fetch;
|
|
15
|
+
}
|
|
16
|
+
export interface ScoperServiceInfo {
|
|
17
|
+
package: typeof SCOPER_PACKAGE;
|
|
18
|
+
version: string;
|
|
19
|
+
sdk: string;
|
|
20
|
+
functionsPackage: string;
|
|
21
|
+
reasoner: ServiceEnv["reasoner"];
|
|
22
|
+
functions: boolean;
|
|
23
|
+
reads: {
|
|
24
|
+
via: "memorix-client-api";
|
|
25
|
+
baseUrl: string;
|
|
26
|
+
};
|
|
27
|
+
writes: {
|
|
28
|
+
via: "memorix-service";
|
|
29
|
+
baseUrl: string;
|
|
30
|
+
};
|
|
31
|
+
aligned: {
|
|
32
|
+
memorixClientApiClient: string;
|
|
33
|
+
memorixClient: string;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* True for the routes a host should pass to `handler`. `GET /health` is not one:
|
|
38
|
+
* a host that mounts Scoper keeps its own health and can report `info` there.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isScoperRoute(method: string | undefined, path: string): boolean;
|
|
41
|
+
export declare function createScoperService(options?: ServiceOptions): {
|
|
42
|
+
server: import("http").Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
43
|
+
handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
44
|
+
scoper: {
|
|
45
|
+
scope: (request: Parameters<typeof import("@x12i/memorix-scoper").scope>[0], context: Parameters<typeof import("@x12i/memorix-scoper").scope>[1], call?: Parameters<typeof import("@x12i/memorix-scoper").scope>[3]) => Promise<{
|
|
46
|
+
items: {
|
|
47
|
+
id: string;
|
|
48
|
+
ref: {
|
|
49
|
+
kind: "subject";
|
|
50
|
+
orgId: string;
|
|
51
|
+
objectType: string;
|
|
52
|
+
contentType: string;
|
|
53
|
+
recordId: string;
|
|
54
|
+
revision?: string | number | undefined;
|
|
55
|
+
} | {
|
|
56
|
+
kind: "estate-bag";
|
|
57
|
+
orgId: string;
|
|
58
|
+
sessionId: string;
|
|
59
|
+
bagId: string;
|
|
60
|
+
path?: string | undefined;
|
|
61
|
+
} | {
|
|
62
|
+
kind: "knowledge-explore";
|
|
63
|
+
orgId: string;
|
|
64
|
+
exploreQueryId: string;
|
|
65
|
+
nodeIds?: string[] | undefined;
|
|
66
|
+
};
|
|
67
|
+
summary: string;
|
|
68
|
+
epistemic: "OBSERVED" | "INFERRED" | "POLICY_CANDIDATE" | "REACHABLE" | "UNKNOWN" | "CONFLICTING";
|
|
69
|
+
confidence?: number | undefined;
|
|
70
|
+
payload?: unknown;
|
|
71
|
+
tags?: string[] | undefined;
|
|
72
|
+
}[];
|
|
73
|
+
conflicts: {
|
|
74
|
+
message: string;
|
|
75
|
+
id: string;
|
|
76
|
+
evidenceIds: string[];
|
|
77
|
+
severity: "low" | "medium" | "high";
|
|
78
|
+
}[];
|
|
79
|
+
unknowns: {
|
|
80
|
+
message: string;
|
|
81
|
+
id: string;
|
|
82
|
+
relatedEvidenceIds?: string[] | undefined;
|
|
83
|
+
}[];
|
|
84
|
+
warnings: {
|
|
85
|
+
code: string;
|
|
86
|
+
message: string;
|
|
87
|
+
details?: unknown;
|
|
88
|
+
}[];
|
|
89
|
+
continuations: {
|
|
90
|
+
kind: "follow-up-question" | "estate-session" | "explore" | "manual";
|
|
91
|
+
message: string;
|
|
92
|
+
payload?: unknown;
|
|
93
|
+
}[];
|
|
94
|
+
answer?: string | undefined;
|
|
95
|
+
budgetsUsed?: {
|
|
96
|
+
subjects?: number | undefined;
|
|
97
|
+
evidenceItems?: number | undefined;
|
|
98
|
+
parallelCalls?: number | undefined;
|
|
99
|
+
tokens?: number | undefined;
|
|
100
|
+
wallClockMs?: number | undefined;
|
|
101
|
+
} | undefined;
|
|
102
|
+
}>;
|
|
103
|
+
scopeByIntent: (request: Parameters<typeof import("@x12i/memorix-scoper").scopeByIntent>[0], context: Parameters<typeof import("@x12i/memorix-scoper").scopeByIntent>[1]) => Promise<{
|
|
104
|
+
anchor: {
|
|
105
|
+
contentTypes: {
|
|
106
|
+
id: string;
|
|
107
|
+
properties: string[];
|
|
108
|
+
}[];
|
|
109
|
+
objectType: string;
|
|
110
|
+
recordIds: string[];
|
|
111
|
+
};
|
|
112
|
+
intent: string;
|
|
113
|
+
associations: {
|
|
114
|
+
contentTypes: {
|
|
115
|
+
id: string;
|
|
116
|
+
properties: string[];
|
|
117
|
+
}[];
|
|
118
|
+
objectType: string;
|
|
119
|
+
field: string;
|
|
120
|
+
recordIds?: string[] | undefined;
|
|
121
|
+
}[];
|
|
122
|
+
warnings?: string[] | undefined;
|
|
123
|
+
}>;
|
|
124
|
+
listIntents: () => {
|
|
125
|
+
id: string;
|
|
126
|
+
title: string;
|
|
127
|
+
contentTypes: string[];
|
|
128
|
+
objectType: string;
|
|
129
|
+
relations: {
|
|
130
|
+
contentTypes: string[];
|
|
131
|
+
objectType: string;
|
|
132
|
+
field: string;
|
|
133
|
+
sourceContentType?: string | undefined;
|
|
134
|
+
}[];
|
|
135
|
+
}[];
|
|
136
|
+
deps: import("@x12i/memorix-scoper").ScoperDependencies;
|
|
137
|
+
};
|
|
138
|
+
info: ScoperServiceInfo;
|
|
139
|
+
listen(portOverride?: number): Promise<{
|
|
140
|
+
port: number;
|
|
141
|
+
host: string;
|
|
142
|
+
}>;
|
|
143
|
+
close(): Promise<void>;
|
|
144
|
+
};
|
|
145
|
+
export type ScoperService = ReturnType<typeof createScoperService>;
|
|
146
|
+
//# sourceMappingURL=create-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-service.d.ts","sourceRoot":"","sources":["../src/create-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAEpF,OAAO,KAAK,EAAE,mBAAmB,EAAU,MAAM,sBAAsB,CAAC;AAKxE,OAAO,EAA+B,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGhF,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAExF,QAAA,MAAM,cAAc,yBAAyB,CAAC;AAE9C,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8GAA8G;IAC9G,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,qDAAqD;IACrD,aAAa,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC7C,oEAAoE;IACpE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,cAAc,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE;QAAE,GAAG,EAAE,oBAAoB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,MAAM,EAAE;QAAE,GAAG,EAAE,iBAAiB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACpD,OAAO,EAAE;QAAE,sBAAsB,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;CACpE;AAqCD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAG/E;AAED,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,cAAmB;;mBA8BlC,eAAe,OAAO,cAAc,KAAG,OAAO,CAAC,IAAI,CAAC;;8JAjE3E,CAAC;;;;;;;;;4BAQkB,CAAC;;;;;;wBAKzB,CADG;;;;;2BAML,CAAA;;;;0BAIU,CAAA;uBACO,CAAC;oBAIjB,CAAD;;;;;;;;;;;kCAI2D,CAAC;;;;;uBAIf,CAAA;;;;;uBAKrB,CAAC;;kBAAsC,CAAC;uBAAyC,CAAC;wBACtG,CAAH;6BAEkC,CAAC;6BACjB,CAAC;sBAEb,CAAC;2BACmC,CAAC;;;;;;;;;;;;;;;;;;;;yBAmBxC,CADiF;;oBACjC,CAAC;;;;;;;;;;;iCAQ5B,CAAC;;;;;;0BAoEA,MAAM;cAGC,MAAM;cAAQ,MAAM;;;EAapD;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
|