@dbx-tools/appkit-graphiti 0.6.113
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 +77 -0
- package/index.ts +10 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +9 -0
- package/lib/src/config.d.ts +56 -0
- package/lib/src/config.js +56 -0
- package/lib/src/plugin.d.ts +35 -0
- package/lib/src/plugin.js +194 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +69 -0
- package/src/config.ts +99 -0
- package/src/plugin.ts +236 -0
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# `@dbx-tools/appkit-graphiti`
|
|
2
|
+
|
|
3
|
+
AppKit process plugin for the Python `dbx-tools-graphiti` MCP runtime.
|
|
4
|
+
|
|
5
|
+
## Key features
|
|
6
|
+
|
|
7
|
+
- starts Graphiti as a supervised AppKit sidecar;
|
|
8
|
+
- inherits the Lakebase environment resolved by `@dbx-tools/appkit` and native
|
|
9
|
+
`lakebase()`, enabling the Python Postgres write journal automatically;
|
|
10
|
+
- starts mise-managed Caddy on `DATABRICKS_APP_PORT`;
|
|
11
|
+
- routes `/graphiti/*` to Graphiti and all other traffic to AppKit;
|
|
12
|
+
- selects a free loopback port for Graphiti unless one is explicitly configured;
|
|
13
|
+
- discovers the upstream MCP tools and exposes them through
|
|
14
|
+
`plugins.graphiti?.toolkit()` for Mastra and AppKit agents;
|
|
15
|
+
- binds both child processes to the shared concurrently-style supervisor so
|
|
16
|
+
signals propagate and any child exit tears down the app;
|
|
17
|
+
- exposes the internal AppKit port and public MCP path through plugin exports.
|
|
18
|
+
|
|
19
|
+
## Why use this over native AppKit
|
|
20
|
+
|
|
21
|
+
AppKit has no Graphiti or embedded MCP sidecar. This package owns AppKit process
|
|
22
|
+
lifecycle and single-port routing while `dbx-tools-graphiti` owns
|
|
23
|
+
Graphiti, Neo4j, LiteLLM, and Postgres recovery.
|
|
24
|
+
|
|
25
|
+
## Register
|
|
26
|
+
|
|
27
|
+
Install `dbx-tools-graphiti` in the app's Python dependencies, then register
|
|
28
|
+
native `lakebase()` before the Graphiti plugin:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { lakebase, server } from "@databricks/appkit";
|
|
32
|
+
import { appkit } from "@dbx-tools/appkit";
|
|
33
|
+
import { config as graphitiConfig, plugin as graphitiPlugin } from "@dbx-tools/appkit-graphiti";
|
|
34
|
+
|
|
35
|
+
const graphiti = graphitiConfig.resolveGraphitiConfig();
|
|
36
|
+
|
|
37
|
+
await appkit.createApp({
|
|
38
|
+
plugins: [
|
|
39
|
+
server({ host: "127.0.0.1", port: graphiti.appPort }),
|
|
40
|
+
lakebase(),
|
|
41
|
+
graphitiPlugin.graphiti(graphiti),
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The public MCP endpoint is `/graphiti/mcp/`. Caddy listens on the platform's
|
|
47
|
+
`DATABRICKS_APP_PORT`; AppKit and Graphiti remain loopback-only.
|
|
48
|
+
|
|
49
|
+
Add the MCP tools to a Mastra agent with:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
tools(plugins) {
|
|
53
|
+
return { ...plugins.graphiti?.toolkit() };
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
Plugin config overrides environment values:
|
|
60
|
+
|
|
61
|
+
- `publicPort` / `DATABRICKS_APP_PORT`: Caddy's public port, default `8000`;
|
|
62
|
+
- `appPort` / `GRAPHITI_APP_PORT`: AppKit's internal port, default public + 1;
|
|
63
|
+
- `graphitiPort` / `GRAPHITI_PORT`: Graphiti's internal port; a free loopback
|
|
64
|
+
port is selected automatically when omitted;
|
|
65
|
+
- `routePrefix` / `GRAPHITI_ROUTE_PREFIX`: public Graphiti prefix, default
|
|
66
|
+
`/graphiti`;
|
|
67
|
+
- `python` / `PYTHON`: Python executable, default `python3`;
|
|
68
|
+
- `journalNamespace` / `JOURNAL_NAMESPACE`: Postgres journal namespace, default
|
|
69
|
+
`DATABRICKS_APP_NAME` then `default`.
|
|
70
|
+
|
|
71
|
+
The app must package both Node and Python dependencies. Databricks Apps supports
|
|
72
|
+
this directly with `package.json` plus `requirements.txt`.
|
|
73
|
+
|
|
74
|
+
## Modules
|
|
75
|
+
|
|
76
|
+
- `config` resolves ports, process command, route prefix, and journal namespace;
|
|
77
|
+
- `plugin` starts and supervises Graphiti and Caddy.
|
package/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// GENERATED by projen watch - DO NOT EDIT.
|
|
2
|
+
// Regenerated from the exporting modules in ./src.
|
|
3
|
+
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
|
+
|
|
5
|
+
export const PACKAGE_IDENTIFIER = "@dbx-tools/appkit-graphiti";
|
|
6
|
+
export * as config from "./src/config.ts";
|
|
7
|
+
export * as plugin from "./src/plugin.ts";
|
|
8
|
+
export { GRAPHITI_CONFIG_SCHEMA } from "./src/config.ts";
|
|
9
|
+
export type { GraphitiPluginConfig, ResolvedGraphitiPluginConfig } from "./src/config.ts";
|
|
10
|
+
export { GraphitiPlugin, graphiti } from "./src/plugin.ts";
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const PACKAGE_IDENTIFIER = "@dbx-tools/appkit-graphiti";
|
|
2
|
+
export * as config from "./src/config.ts";
|
|
3
|
+
export * as plugin from "./src/plugin.ts";
|
|
4
|
+
export { GRAPHITI_CONFIG_SCHEMA } from "./src/config.ts";
|
|
5
|
+
export type { GraphitiPluginConfig, ResolvedGraphitiPluginConfig } from "./src/config.ts";
|
|
6
|
+
export { GraphitiPlugin, graphiti } from "./src/plugin.ts";
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// GENERATED by projen watch - DO NOT EDIT.
|
|
2
|
+
// Regenerated from the exporting modules in ./src.
|
|
3
|
+
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
|
+
export const PACKAGE_IDENTIFIER = "@dbx-tools/appkit-graphiti";
|
|
5
|
+
export * as config from "./src/config.js";
|
|
6
|
+
export * as plugin from "./src/plugin.js";
|
|
7
|
+
export { GRAPHITI_CONFIG_SCHEMA } from "./src/config.js";
|
|
8
|
+
export { GraphitiPlugin, graphiti } from "./src/plugin.js";
|
|
9
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxNQUFNLENBQUMsTUFBTSxrQkFBa0IsR0FBRyw0QkFBNEIsQ0FBQztBQUMvRCxPQUFPLEtBQUssTUFBTSxNQUFNLGlCQUFpQixDQUFDO0FBQzFDLE9BQU8sS0FBSyxNQUFNLE1BQU0saUJBQWlCLENBQUM7QUFDMUMsT0FBTyxFQUFFLHNCQUFzQixFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFFekQsT0FBTyxFQUFFLGNBQWMsRUFBRSxRQUFRLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIEdFTkVSQVRFRCBieSBwcm9qZW4gd2F0Y2ggLSBETyBOT1QgRURJVC5cbi8vIFJlZ2VuZXJhdGVkIGZyb20gdGhlIGV4cG9ydGluZyBtb2R1bGVzIGluIC4vc3JjLlxuLy8gSGFuZCBlZGl0cyBhcmUgb3ZlcndyaXR0ZW4gb24gdGhlIG5leHQgd2F0Y2g7IHRoaXMgZmlsZSBpcyByZWFkLW9ubHkuXG5cbmV4cG9ydCBjb25zdCBQQUNLQUdFX0lERU5USUZJRVIgPSBcIkBkYngtdG9vbHMvYXBwa2l0LWdyYXBoaXRpXCI7XG5leHBvcnQgKiBhcyBjb25maWcgZnJvbSBcIi4vc3JjL2NvbmZpZy50c1wiO1xuZXhwb3J0ICogYXMgcGx1Z2luIGZyb20gXCIuL3NyYy9wbHVnaW4udHNcIjtcbmV4cG9ydCB7IEdSQVBISVRJX0NPTkZJR19TQ0hFTUEgfSBmcm9tIFwiLi9zcmMvY29uZmlnLnRzXCI7XG5leHBvcnQgdHlwZSB7IEdyYXBoaXRpUGx1Z2luQ29uZmlnLCBSZXNvbHZlZEdyYXBoaXRpUGx1Z2luQ29uZmlnIH0gZnJvbSBcIi4vc3JjL2NvbmZpZy50c1wiO1xuZXhwb3J0IHsgR3JhcGhpdGlQbHVnaW4sIGdyYXBoaXRpIH0gZnJvbSBcIi4vc3JjL3BsdWdpbi50c1wiO1xuIl19
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppKit Graphiti sidecar ports, route prefix, and Python command.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { type BasePluginConfig } from "@databricks/appkit";
|
|
7
|
+
export interface GraphitiPluginConfig extends BasePluginConfig {
|
|
8
|
+
publicPort?: number;
|
|
9
|
+
appPort?: number;
|
|
10
|
+
graphitiPort?: number;
|
|
11
|
+
routePrefix?: string;
|
|
12
|
+
python?: string;
|
|
13
|
+
journalNamespace?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ResolvedGraphitiPluginConfig extends GraphitiPluginConfig {
|
|
16
|
+
publicPort: number;
|
|
17
|
+
appPort: number;
|
|
18
|
+
graphitiPort: number;
|
|
19
|
+
routePrefix: string;
|
|
20
|
+
python: string;
|
|
21
|
+
journalNamespace: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const GRAPHITI_CONFIG_SCHEMA: {
|
|
24
|
+
type: "object";
|
|
25
|
+
properties: {
|
|
26
|
+
publicPort: {
|
|
27
|
+
type: "integer";
|
|
28
|
+
minimum: number;
|
|
29
|
+
maximum: number;
|
|
30
|
+
};
|
|
31
|
+
appPort: {
|
|
32
|
+
type: "integer";
|
|
33
|
+
minimum: number;
|
|
34
|
+
maximum: number;
|
|
35
|
+
};
|
|
36
|
+
graphitiPort: {
|
|
37
|
+
type: "integer";
|
|
38
|
+
minimum: number;
|
|
39
|
+
maximum: number;
|
|
40
|
+
};
|
|
41
|
+
routePrefix: {
|
|
42
|
+
type: "string";
|
|
43
|
+
};
|
|
44
|
+
python: {
|
|
45
|
+
type: "string";
|
|
46
|
+
};
|
|
47
|
+
journalNamespace: {
|
|
48
|
+
type: "string";
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
additionalProperties: false;
|
|
52
|
+
};
|
|
53
|
+
/** Resolve plugin config over exact environment values and stable defaults. */
|
|
54
|
+
export declare function resolveGraphitiConfig(config?: GraphitiPluginConfig): ResolvedGraphitiPluginConfig;
|
|
55
|
+
/** Return the internal port an AppKit `server()` plugin should bind. */
|
|
56
|
+
export declare function appPort(config?: GraphitiPluginConfig): number;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppKit Graphiti sidecar ports, route prefix, and Python command.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { ConfigurationError } from "@databricks/appkit";
|
|
7
|
+
import { config as coreConfig } from "@dbx-tools/core";
|
|
8
|
+
export const GRAPHITI_CONFIG_SCHEMA = {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
publicPort: { type: "integer", minimum: 1, maximum: 65535 },
|
|
12
|
+
appPort: { type: "integer", minimum: 1, maximum: 65535 },
|
|
13
|
+
graphitiPort: { type: "integer", minimum: 1, maximum: 65535 },
|
|
14
|
+
routePrefix: { type: "string" },
|
|
15
|
+
python: { type: "string" },
|
|
16
|
+
journalNamespace: { type: "string" },
|
|
17
|
+
},
|
|
18
|
+
additionalProperties: false,
|
|
19
|
+
};
|
|
20
|
+
/** Resolve plugin config over exact environment values and stable defaults. */
|
|
21
|
+
export function resolveGraphitiConfig(config = {}) {
|
|
22
|
+
const publicPort = coreConfig.port(config.publicPort, "DATABRICKS_APP_PORT", 8000, coreConfig.ENV_ONLY);
|
|
23
|
+
const appPort = coreConfig.port(config.appPort, "GRAPHITI_APP_PORT", publicPort + 1, coreConfig.ENV_ONLY);
|
|
24
|
+
const graphitiPort = coreConfig.port(config.graphitiPort, "GRAPHITI_PORT", 0, coreConfig.ENV_ONLY);
|
|
25
|
+
if (new Set([publicPort, appPort, graphitiPort]).size !== 3) {
|
|
26
|
+
throw new ConfigurationError("Graphiti public, AppKit, and sidecar ports must be distinct");
|
|
27
|
+
}
|
|
28
|
+
const routePrefix = normalizeRoutePrefix(config.routePrefix ??
|
|
29
|
+
coreConfig.text("GRAPHITI_ROUTE_PREFIX", coreConfig.ENV_ONLY) ??
|
|
30
|
+
"/graphiti");
|
|
31
|
+
const python = config.python ?? coreConfig.text("PYTHON", coreConfig.ENV_ONLY)?.trim() ?? "python3";
|
|
32
|
+
const journalNamespace = config.journalNamespace ??
|
|
33
|
+
coreConfig.text("JOURNAL_NAMESPACE", coreConfig.ENV_ONLY)?.trim() ??
|
|
34
|
+
process.env.DATABRICKS_APP_NAME?.trim() ??
|
|
35
|
+
"default";
|
|
36
|
+
return {
|
|
37
|
+
publicPort,
|
|
38
|
+
appPort,
|
|
39
|
+
graphitiPort,
|
|
40
|
+
routePrefix,
|
|
41
|
+
python,
|
|
42
|
+
journalNamespace,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Return the internal port an AppKit `server()` plugin should bind. */
|
|
46
|
+
export function appPort(config = {}) {
|
|
47
|
+
return resolveGraphitiConfig(config).appPort;
|
|
48
|
+
}
|
|
49
|
+
function normalizeRoutePrefix(value) {
|
|
50
|
+
const normalized = `/${value.trim().replace(/^\/+|\/+$/g, "")}`;
|
|
51
|
+
if (normalized === "/") {
|
|
52
|
+
throw new ConfigurationError("Graphiti routePrefix must contain a path segment");
|
|
53
|
+
}
|
|
54
|
+
return normalized;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbmZpZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7OztHQUlHO0FBQ0gsT0FBTyxFQUFFLGtCQUFrQixFQUF5QixNQUFNLG9CQUFvQixDQUFDO0FBQy9FLE9BQU8sRUFBRSxNQUFNLElBQUksVUFBVSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFxQnZELE1BQU0sQ0FBQyxNQUFNLHNCQUFzQixHQUFHO0lBQ3BDLElBQUksRUFBRSxRQUFRO0lBQ2QsVUFBVSxFQUFFO1FBQ1YsVUFBVSxFQUFFLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsQ0FBQyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUU7UUFDM0QsT0FBTyxFQUFFLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsQ0FBQyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUU7UUFDeEQsWUFBWSxFQUFFLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsQ0FBQyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUU7UUFDN0QsV0FBVyxFQUFFLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRTtRQUMvQixNQUFNLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFO1FBQzFCLGdCQUFnQixFQUFFLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRTtLQUNyQztJQUNELG9CQUFvQixFQUFFLEtBQUs7Q0FDTixDQUFDO0FBRXhCLCtFQUErRTtBQUMvRSxNQUFNLFVBQVUscUJBQXFCLENBQ25DLFNBQStCLEVBQUU7SUFFakMsTUFBTSxVQUFVLEdBQUcsVUFBVSxDQUFDLElBQUksQ0FDaEMsTUFBTSxDQUFDLFVBQVUsRUFDakIscUJBQXFCLEVBQ3JCLElBQUksRUFDSixVQUFVLENBQUMsUUFBUSxDQUNwQixDQUFDO0lBQ0YsTUFBTSxPQUFPLEdBQUcsVUFBVSxDQUFDLElBQUksQ0FDN0IsTUFBTSxDQUFDLE9BQU8sRUFDZCxtQkFBbUIsRUFDbkIsVUFBVSxHQUFHLENBQUMsRUFDZCxVQUFVLENBQUMsUUFBUSxDQUNwQixDQUFDO0lBQ0YsTUFBTSxZQUFZLEdBQUcsVUFBVSxDQUFDLElBQUksQ0FDbEMsTUFBTSxDQUFDLFlBQVksRUFDbkIsZUFBZSxFQUNmLENBQUMsRUFDRCxVQUFVLENBQUMsUUFBUSxDQUNwQixDQUFDO0lBQ0YsSUFBSSxJQUFJLEdBQUcsQ0FBQyxDQUFDLFVBQVUsRUFBRSxPQUFPLEVBQUUsWUFBWSxDQUFDLENBQUMsQ0FBQyxJQUFJLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDNUQsTUFBTSxJQUFJLGtCQUFrQixDQUFDLDZEQUE2RCxDQUFDLENBQUM7SUFDOUYsQ0FBQztJQUNELE1BQU0sV0FBVyxHQUFHLG9CQUFvQixDQUN0QyxNQUFNLENBQUMsV0FBVztRQUNoQixVQUFVLENBQUMsSUFBSSxDQUFDLHVCQUF1QixFQUFFLFVBQVUsQ0FBQyxRQUFRLENBQUM7UUFDN0QsV0FBVyxDQUNkLENBQUM7SUFDRixNQUFNLE1BQU0sR0FDVixNQUFNLENBQUMsTUFBTSxJQUFJLFVBQVUsQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLFVBQVUsQ0FBQyxRQUFRLENBQUMsRUFBRSxJQUFJLEVBQUUsSUFBSSxTQUFTLENBQUM7SUFDdkYsTUFBTSxnQkFBZ0IsR0FDcEIsTUFBTSxDQUFDLGdCQUFnQjtRQUN2QixVQUFVLENBQUMsSUFBSSxDQUFDLG1CQUFtQixFQUFFLFVBQVUsQ0FBQyxRQUFRLENBQUMsRUFBRSxJQUFJLEVBQUU7UUFDakUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxtQkFBbUIsRUFBRSxJQUFJLEVBQUU7UUFDdkMsU0FBUyxDQUFDO0lBQ1osT0FBTztRQUNMLFVBQVU7UUFDVixPQUFPO1FBQ1AsWUFBWTtRQUNaLFdBQVc7UUFDWCxNQUFNO1FBQ04sZ0JBQWdCO0tBQ2pCLENBQUM7QUFDSixDQUFDO0FBRUQsd0VBQXdFO0FBQ3hFLE1BQU0sVUFBVSxPQUFPLENBQUMsU0FBK0IsRUFBRTtJQUN2RCxPQUFPLHFCQUFxQixDQUFDLE1BQU0sQ0FBQyxDQUFDLE9BQU8sQ0FBQztBQUMvQyxDQUFDO0FBRUQsU0FBUyxvQkFBb0IsQ0FBQyxLQUFhO0lBQ3pDLE1BQU0sVUFBVSxHQUFHLElBQUksS0FBSyxDQUFDLElBQUksRUFBRSxDQUFDLE9BQU8sQ0FBQyxZQUFZLEVBQUUsRUFBRSxDQUFDLEVBQUUsQ0FBQztJQUNoRSxJQUFJLFVBQVUsS0FBSyxHQUFHLEVBQUUsQ0FBQztRQUN2QixNQUFNLElBQUksa0JBQWtCLENBQUMsa0RBQWtELENBQUMsQ0FBQztJQUNuRixDQUFDO0lBQ0QsT0FBTyxVQUFVLENBQUM7QUFDcEIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQXBwS2l0IEdyYXBoaXRpIHNpZGVjYXIgcG9ydHMsIHJvdXRlIHByZWZpeCwgYW5kIFB5dGhvbiBjb21tYW5kLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuaW1wb3J0IHsgQ29uZmlndXJhdGlvbkVycm9yLCB0eXBlIEJhc2VQbHVnaW5Db25maWcgfSBmcm9tIFwiQGRhdGFicmlja3MvYXBwa2l0XCI7XG5pbXBvcnQgeyBjb25maWcgYXMgY29yZUNvbmZpZyB9IGZyb20gXCJAZGJ4LXRvb2xzL2NvcmVcIjtcbmltcG9ydCB0eXBlIHsgSlNPTlNjaGVtYTcgfSBmcm9tIFwianNvbi1zY2hlbWFcIjtcblxuZXhwb3J0IGludGVyZmFjZSBHcmFwaGl0aVBsdWdpbkNvbmZpZyBleHRlbmRzIEJhc2VQbHVnaW5Db25maWcge1xuICBwdWJsaWNQb3J0PzogbnVtYmVyO1xuICBhcHBQb3J0PzogbnVtYmVyO1xuICBncmFwaGl0aVBvcnQ/OiBudW1iZXI7XG4gIHJvdXRlUHJlZml4Pzogc3RyaW5nO1xuICBweXRob24/OiBzdHJpbmc7XG4gIGpvdXJuYWxOYW1lc3BhY2U/OiBzdHJpbmc7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmVzb2x2ZWRHcmFwaGl0aVBsdWdpbkNvbmZpZyBleHRlbmRzIEdyYXBoaXRpUGx1Z2luQ29uZmlnIHtcbiAgcHVibGljUG9ydDogbnVtYmVyO1xuICBhcHBQb3J0OiBudW1iZXI7XG4gIGdyYXBoaXRpUG9ydDogbnVtYmVyO1xuICByb3V0ZVByZWZpeDogc3RyaW5nO1xuICBweXRob246IHN0cmluZztcbiAgam91cm5hbE5hbWVzcGFjZTogc3RyaW5nO1xufVxuXG5leHBvcnQgY29uc3QgR1JBUEhJVElfQ09ORklHX1NDSEVNQSA9IHtcbiAgdHlwZTogXCJvYmplY3RcIixcbiAgcHJvcGVydGllczoge1xuICAgIHB1YmxpY1BvcnQ6IHsgdHlwZTogXCJpbnRlZ2VyXCIsIG1pbmltdW06IDEsIG1heGltdW06IDY1NTM1IH0sXG4gICAgYXBwUG9ydDogeyB0eXBlOiBcImludGVnZXJcIiwgbWluaW11bTogMSwgbWF4aW11bTogNjU1MzUgfSxcbiAgICBncmFwaGl0aVBvcnQ6IHsgdHlwZTogXCJpbnRlZ2VyXCIsIG1pbmltdW06IDEsIG1heGltdW06IDY1NTM1IH0sXG4gICAgcm91dGVQcmVmaXg6IHsgdHlwZTogXCJzdHJpbmdcIiB9LFxuICAgIHB5dGhvbjogeyB0eXBlOiBcInN0cmluZ1wiIH0sXG4gICAgam91cm5hbE5hbWVzcGFjZTogeyB0eXBlOiBcInN0cmluZ1wiIH0sXG4gIH0sXG4gIGFkZGl0aW9uYWxQcm9wZXJ0aWVzOiBmYWxzZSxcbn0gc2F0aXNmaWVzIEpTT05TY2hlbWE3O1xuXG4vKiogUmVzb2x2ZSBwbHVnaW4gY29uZmlnIG92ZXIgZXhhY3QgZW52aXJvbm1lbnQgdmFsdWVzIGFuZCBzdGFibGUgZGVmYXVsdHMuICovXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZUdyYXBoaXRpQ29uZmlnKFxuICBjb25maWc6IEdyYXBoaXRpUGx1Z2luQ29uZmlnID0ge30sXG4pOiBSZXNvbHZlZEdyYXBoaXRpUGx1Z2luQ29uZmlnIHtcbiAgY29uc3QgcHVibGljUG9ydCA9IGNvcmVDb25maWcucG9ydChcbiAgICBjb25maWcucHVibGljUG9ydCxcbiAgICBcIkRBVEFCUklDS1NfQVBQX1BPUlRcIixcbiAgICA4MDAwLFxuICAgIGNvcmVDb25maWcuRU5WX09OTFksXG4gICk7XG4gIGNvbnN0IGFwcFBvcnQgPSBjb3JlQ29uZmlnLnBvcnQoXG4gICAgY29uZmlnLmFwcFBvcnQsXG4gICAgXCJHUkFQSElUSV9BUFBfUE9SVFwiLFxuICAgIHB1YmxpY1BvcnQgKyAxLFxuICAgIGNvcmVDb25maWcuRU5WX09OTFksXG4gICk7XG4gIGNvbnN0IGdyYXBoaXRpUG9ydCA9IGNvcmVDb25maWcucG9ydChcbiAgICBjb25maWcuZ3JhcGhpdGlQb3J0LFxuICAgIFwiR1JBUEhJVElfUE9SVFwiLFxuICAgIDAsXG4gICAgY29yZUNvbmZpZy5FTlZfT05MWSxcbiAgKTtcbiAgaWYgKG5ldyBTZXQoW3B1YmxpY1BvcnQsIGFwcFBvcnQsIGdyYXBoaXRpUG9ydF0pLnNpemUgIT09IDMpIHtcbiAgICB0aHJvdyBuZXcgQ29uZmlndXJhdGlvbkVycm9yKFwiR3JhcGhpdGkgcHVibGljLCBBcHBLaXQsIGFuZCBzaWRlY2FyIHBvcnRzIG11c3QgYmUgZGlzdGluY3RcIik7XG4gIH1cbiAgY29uc3Qgcm91dGVQcmVmaXggPSBub3JtYWxpemVSb3V0ZVByZWZpeChcbiAgICBjb25maWcucm91dGVQcmVmaXggPz9cbiAgICAgIGNvcmVDb25maWcudGV4dChcIkdSQVBISVRJX1JPVVRFX1BSRUZJWFwiLCBjb3JlQ29uZmlnLkVOVl9PTkxZKSA/P1xuICAgICAgXCIvZ3JhcGhpdGlcIixcbiAgKTtcbiAgY29uc3QgcHl0aG9uID1cbiAgICBjb25maWcucHl0aG9uID8/IGNvcmVDb25maWcudGV4dChcIlBZVEhPTlwiLCBjb3JlQ29uZmlnLkVOVl9PTkxZKT8udHJpbSgpID8/IFwicHl0aG9uM1wiO1xuICBjb25zdCBqb3VybmFsTmFtZXNwYWNlID1cbiAgICBjb25maWcuam91cm5hbE5hbWVzcGFjZSA/P1xuICAgIGNvcmVDb25maWcudGV4dChcIkpPVVJOQUxfTkFNRVNQQUNFXCIsIGNvcmVDb25maWcuRU5WX09OTFkpPy50cmltKCkgPz9cbiAgICBwcm9jZXNzLmVudi5EQVRBQlJJQ0tTX0FQUF9OQU1FPy50cmltKCkgPz9cbiAgICBcImRlZmF1bHRcIjtcbiAgcmV0dXJuIHtcbiAgICBwdWJsaWNQb3J0LFxuICAgIGFwcFBvcnQsXG4gICAgZ3JhcGhpdGlQb3J0LFxuICAgIHJvdXRlUHJlZml4LFxuICAgIHB5dGhvbixcbiAgICBqb3VybmFsTmFtZXNwYWNlLFxuICB9O1xufVxuXG4vKiogUmV0dXJuIHRoZSBpbnRlcm5hbCBwb3J0IGFuIEFwcEtpdCBgc2VydmVyKClgIHBsdWdpbiBzaG91bGQgYmluZC4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhcHBQb3J0KGNvbmZpZzogR3JhcGhpdGlQbHVnaW5Db25maWcgPSB7fSk6IG51bWJlciB7XG4gIHJldHVybiByZXNvbHZlR3JhcGhpdGlDb25maWcoY29uZmlnKS5hcHBQb3J0O1xufVxuXG5mdW5jdGlvbiBub3JtYWxpemVSb3V0ZVByZWZpeCh2YWx1ZTogc3RyaW5nKTogc3RyaW5nIHtcbiAgY29uc3Qgbm9ybWFsaXplZCA9IGAvJHt2YWx1ZS50cmltKCkucmVwbGFjZSgvXlxcLyt8XFwvKyQvZywgXCJcIil9YDtcbiAgaWYgKG5vcm1hbGl6ZWQgPT09IFwiL1wiKSB7XG4gICAgdGhyb3cgbmV3IENvbmZpZ3VyYXRpb25FcnJvcihcIkdyYXBoaXRpIHJvdXRlUHJlZml4IG11c3QgY29udGFpbiBhIHBhdGggc2VnbWVudFwiKTtcbiAgfVxuICByZXR1cm4gbm9ybWFsaXplZDtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Plugin, type PluginManifest, type ResourceRequirement } from "@databricks/appkit";
|
|
2
|
+
import { type GraphitiPluginConfig } from "./config.ts";
|
|
3
|
+
interface ToolkitEntry {
|
|
4
|
+
pluginName: string;
|
|
5
|
+
localName: string;
|
|
6
|
+
def: {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
parameters: unknown;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export declare class GraphitiPlugin extends Plugin<GraphitiPluginConfig> {
|
|
13
|
+
static manifest: PluginManifest<"graphiti">;
|
|
14
|
+
static getResourceRequirements(): ResourceRequirement[];
|
|
15
|
+
private readonly logger;
|
|
16
|
+
private children;
|
|
17
|
+
private mcp?;
|
|
18
|
+
private mcpTools;
|
|
19
|
+
private resolved?;
|
|
20
|
+
private stopping;
|
|
21
|
+
setup(): Promise<void>;
|
|
22
|
+
abortActiveOperations(): void;
|
|
23
|
+
exports(): {
|
|
24
|
+
appPort: number;
|
|
25
|
+
graphitiPort: number;
|
|
26
|
+
mcpPath: string;
|
|
27
|
+
publicPort: number;
|
|
28
|
+
routePrefix: string;
|
|
29
|
+
};
|
|
30
|
+
toolkit(): Record<string, ToolkitEntry>;
|
|
31
|
+
executeAgentTool(name: string, args: unknown, signal?: AbortSignal): Promise<unknown>;
|
|
32
|
+
private stopGraphiti;
|
|
33
|
+
}
|
|
34
|
+
export declare const graphiti: import("@databricks/appkit").ToPlugin<typeof GraphitiPlugin, GraphitiPluginConfig, "graphiti">;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppKit plugin that supervises Graphiti and a Caddy single-port proxy.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { createServer } from "node:net";
|
|
7
|
+
import { Plugin, lakebase, toPlugin, } from "@databricks/appkit";
|
|
8
|
+
import { interceptor as appkitInterceptor, plugin as appkitPlugin } from "@dbx-tools/appkit";
|
|
9
|
+
import { exec } from "@dbx-tools/core";
|
|
10
|
+
import { async as asyncModule, log } from "@dbx-tools/shared-core";
|
|
11
|
+
import { MCPClient } from "@mastra/mcp";
|
|
12
|
+
import { GRAPHITI_CONFIG_SCHEMA, resolveGraphitiConfig, } from "./config.js";
|
|
13
|
+
const LAKEBASE_MANIFEST = appkitPlugin.data(lakebase).plugin.manifest;
|
|
14
|
+
export class GraphitiPlugin extends Plugin {
|
|
15
|
+
static manifest = {
|
|
16
|
+
name: "graphiti",
|
|
17
|
+
displayName: "Graphiti",
|
|
18
|
+
description: "Runs the dbx-tools Graphiti MCP sidecar with Lakebase-backed recovery and " +
|
|
19
|
+
"publishes it through the App's single port with Caddy.",
|
|
20
|
+
stability: "beta",
|
|
21
|
+
resources: {
|
|
22
|
+
required: [],
|
|
23
|
+
optional: [...LAKEBASE_MANIFEST.resources.required],
|
|
24
|
+
},
|
|
25
|
+
config: { schema: GRAPHITI_CONFIG_SCHEMA },
|
|
26
|
+
};
|
|
27
|
+
static getResourceRequirements() {
|
|
28
|
+
return LAKEBASE_MANIFEST.resources.required.map((resource) => ({
|
|
29
|
+
...resource,
|
|
30
|
+
required: true,
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
logger = log.logger(this);
|
|
34
|
+
children = [];
|
|
35
|
+
mcp;
|
|
36
|
+
mcpTools = {};
|
|
37
|
+
resolved;
|
|
38
|
+
stopping = false;
|
|
39
|
+
async setup() {
|
|
40
|
+
const configured = resolveGraphitiConfig(this.config);
|
|
41
|
+
this.resolved = {
|
|
42
|
+
...configured,
|
|
43
|
+
graphitiPort: configured.graphitiPort || (await availablePort()),
|
|
44
|
+
};
|
|
45
|
+
if (!process.env.LAKEBASE_ENDPOINT && !process.env.PGHOST) {
|
|
46
|
+
throw new Error("Graphiti requires Lakebase; register lakebase() before graphiti() and bind its Postgres resource");
|
|
47
|
+
}
|
|
48
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
49
|
+
process.once(signal, () => this.stopGraphiti());
|
|
50
|
+
}
|
|
51
|
+
const supervisor = appkitInterceptor.createInterceptorContext({});
|
|
52
|
+
supervisor.context.onTeardown(() => this.stopGraphiti());
|
|
53
|
+
const graphiti = exec.spawn(this.resolved.python, ["-m", "dbx_tools.graphiti", "start"], {
|
|
54
|
+
env: {
|
|
55
|
+
...process.env,
|
|
56
|
+
GRAPHITI_HOST: "127.0.0.1",
|
|
57
|
+
GRAPHITI_PORT: String(this.resolved.graphitiPort),
|
|
58
|
+
JOURNAL_NAMESPACE: this.resolved.journalNamespace,
|
|
59
|
+
},
|
|
60
|
+
stdin: "ignore",
|
|
61
|
+
stdout: "inherit",
|
|
62
|
+
stderr: "inherit",
|
|
63
|
+
});
|
|
64
|
+
const caddy = exec.spawn(this.resolved.python, [
|
|
65
|
+
"-m",
|
|
66
|
+
"dbx_tools.graphiti.proxy",
|
|
67
|
+
"--public-port",
|
|
68
|
+
String(this.resolved.publicPort),
|
|
69
|
+
"--app-port",
|
|
70
|
+
String(this.resolved.appPort),
|
|
71
|
+
"--graphiti-port",
|
|
72
|
+
String(this.resolved.graphitiPort),
|
|
73
|
+
"--route-prefix",
|
|
74
|
+
this.resolved.routePrefix,
|
|
75
|
+
], {
|
|
76
|
+
env: process.env,
|
|
77
|
+
stdin: "ignore",
|
|
78
|
+
stdout: "inherit",
|
|
79
|
+
stderr: "inherit",
|
|
80
|
+
});
|
|
81
|
+
this.children = [graphiti, caddy];
|
|
82
|
+
for (const child of this.children)
|
|
83
|
+
supervisor.context.bindProcess(child);
|
|
84
|
+
await waitForGraphiti(this.resolved.graphitiPort);
|
|
85
|
+
this.mcp = new MCPClient({
|
|
86
|
+
id: `appkit-graphiti-${this.resolved.graphitiPort}`,
|
|
87
|
+
servers: {
|
|
88
|
+
graphiti: {
|
|
89
|
+
url: new URL(`http://127.0.0.1:${this.resolved.graphitiPort}/mcp`),
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
const discovered = await this.mcp.listTools();
|
|
94
|
+
this.mcpTools = Object.fromEntries(Object.entries(discovered).map(([name, tool]) => [
|
|
95
|
+
name.replace(/^graphiti_/, ""),
|
|
96
|
+
tool,
|
|
97
|
+
]));
|
|
98
|
+
this.logger.info("ready", {
|
|
99
|
+
appPort: this.resolved.appPort,
|
|
100
|
+
graphitiPort: this.resolved.graphitiPort,
|
|
101
|
+
publicPort: this.resolved.publicPort,
|
|
102
|
+
mcpPath: `${this.resolved.routePrefix}/mcp/`,
|
|
103
|
+
tools: Object.keys(this.mcpTools),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
abortActiveOperations() {
|
|
107
|
+
super.abortActiveOperations();
|
|
108
|
+
this.stopGraphiti();
|
|
109
|
+
void this.mcp?.disconnect();
|
|
110
|
+
this.mcp = undefined;
|
|
111
|
+
this.mcpTools = {};
|
|
112
|
+
for (const child of this.children) {
|
|
113
|
+
if (!child.killed)
|
|
114
|
+
child.kill("SIGTERM");
|
|
115
|
+
}
|
|
116
|
+
this.children = [];
|
|
117
|
+
}
|
|
118
|
+
exports() {
|
|
119
|
+
const resolved = this.resolved ?? resolveGraphitiConfig(this.config);
|
|
120
|
+
return {
|
|
121
|
+
appPort: resolved.appPort,
|
|
122
|
+
graphitiPort: resolved.graphitiPort,
|
|
123
|
+
mcpPath: `${resolved.routePrefix}/mcp/`,
|
|
124
|
+
publicPort: resolved.publicPort,
|
|
125
|
+
routePrefix: resolved.routePrefix,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
toolkit() {
|
|
129
|
+
return Object.fromEntries(Object.entries(this.mcpTools).map(([name, tool]) => [
|
|
130
|
+
name,
|
|
131
|
+
{
|
|
132
|
+
pluginName: "graphiti",
|
|
133
|
+
localName: name,
|
|
134
|
+
def: {
|
|
135
|
+
name,
|
|
136
|
+
description: tool.description,
|
|
137
|
+
parameters: tool.inputSchema ?? { type: "object", properties: {} },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
]));
|
|
141
|
+
}
|
|
142
|
+
async executeAgentTool(name, args, signal) {
|
|
143
|
+
const tool = this.mcpTools[name];
|
|
144
|
+
if (!tool?.execute)
|
|
145
|
+
throw new Error(`Unknown Graphiti tool: ${name}`);
|
|
146
|
+
return tool.execute(args, { abortSignal: signal });
|
|
147
|
+
}
|
|
148
|
+
stopGraphiti() {
|
|
149
|
+
if (this.stopping || !this.resolved)
|
|
150
|
+
return;
|
|
151
|
+
this.stopping = true;
|
|
152
|
+
void exec.spawn(this.resolved.python, ["-m", "dbx_tools.graphiti", "down"], {
|
|
153
|
+
env: process.env,
|
|
154
|
+
stdin: "ignore",
|
|
155
|
+
stdout: "inherit",
|
|
156
|
+
stderr: "inherit",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export const graphiti = toPlugin(GraphitiPlugin);
|
|
161
|
+
async function waitForGraphiti(port) {
|
|
162
|
+
for await (const ready of asyncModule.poll(async ({ signal }) => {
|
|
163
|
+
try {
|
|
164
|
+
const response = await fetch(`http://127.0.0.1:${port}/health`, { signal });
|
|
165
|
+
return response.ok;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}, {
|
|
171
|
+
intervalMs: 250,
|
|
172
|
+
timeoutMs: 60_000,
|
|
173
|
+
predicate: (ready) => !ready,
|
|
174
|
+
})) {
|
|
175
|
+
if (ready)
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function availablePort() {
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
const server = createServer();
|
|
182
|
+
server.once("error", reject);
|
|
183
|
+
server.listen(0, "127.0.0.1", () => {
|
|
184
|
+
const address = server.address();
|
|
185
|
+
if (!address || typeof address === "string") {
|
|
186
|
+
server.close();
|
|
187
|
+
reject(new Error("Could not allocate a Graphiti loopback port"));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
server.close((error) => (error ? reject(error) : resolve(address.port)));
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2luLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3BsdWdpbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7OztHQUlHO0FBQ0gsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLFVBQVUsQ0FBQztBQUN4QyxPQUFPLEVBQ0wsTUFBTSxFQUNOLFFBQVEsRUFDUixRQUFRLEdBR1QsTUFBTSxvQkFBb0IsQ0FBQztBQUM1QixPQUFPLEVBQUUsV0FBVyxJQUFJLGlCQUFpQixFQUFFLE1BQU0sSUFBSSxZQUFZLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUM3RixPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDdkMsT0FBTyxFQUFFLEtBQUssSUFBSSxXQUFXLEVBQUUsR0FBRyxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFFbkUsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUN4QyxPQUFPLEVBQ0wsc0JBQXNCLEVBQ3RCLHFCQUFxQixHQUd0QixNQUFNLGFBQWEsQ0FBQztBQUVyQixNQUFNLGlCQUFpQixHQUFHLFlBQVksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQztBQVl0RSxNQUFNLE9BQU8sY0FBZSxTQUFRLE1BQTRCO0lBQzlELE1BQU0sQ0FBQyxRQUFRLEdBQStCO1FBQzVDLElBQUksRUFBRSxVQUFVO1FBQ2hCLFdBQVcsRUFBRSxVQUFVO1FBQ3ZCLFdBQVcsRUFDVCw0RUFBNEU7WUFDNUUsd0RBQXdEO1FBQzFELFNBQVMsRUFBRSxNQUFNO1FBQ2pCLFNBQVMsRUFBRTtZQUNULFFBQVEsRUFBRSxFQUFFO1lBQ1osUUFBUSxFQUFFLENBQUMsR0FBRyxpQkFBaUIsQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDO1NBQ3BEO1FBQ0QsTUFBTSxFQUFFLEVBQUUsTUFBTSxFQUFFLHNCQUFzQixFQUFFO0tBQzNDLENBQUM7SUFFRixNQUFNLENB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RSxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDM0UsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEFwcEtpdCBwbHVnaW4gdGhhdCBzdXBlcnZpc2VzIEdyYXBoaXRpIGFuZCBhIENhZGR5IHNpbmdsZS1wb3J0IHByb3h5LlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuaW1wb3J0IHsgY3JlYXRlU2VydmVyIH0gZnJvbSBcIm5vZGU6bmV0XCI7XG5pbXBvcnQge1xuICBQbHVnaW4sXG4gIGxha2ViYXNlLFxuICB0b1BsdWdpbixcbiAgdHlwZSBQbHVnaW5NYW5pZmVzdCxcbiAgdHlwZSBSZXNvdXJjZVJlcXVpcmVtZW50LFxufSBmcm9tIFwiQGRhdGFicmlja3MvYXBwa2l0XCI7XG5pbXBvcnQgeyBpbnRlcmNlcHRvciBhcyBhcHBraXRJbnRlcmNlcHRvciwgcGx1Z2luIGFzIGFwcGtpdFBsdWdpbiB9IGZyb20gXCJAZGJ4LXRvb2xzL2FwcGtpdFwiO1xuaW1wb3J0IHsgZXhlYyB9IGZyb20gXCJAZGJ4LXRvb2xzL2NvcmVcIjtcbmltcG9ydCB7IGFzeW5jIGFzIGFzeW5jTW9kdWxlLCBsb2cgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHR5cGUgeyBUb29sIH0gZnJvbSBcIkBtYXN0cmEvY29yZS90b29sc1wiO1xuaW1wb3J0IHsgTUNQQ2xpZW50IH0gZnJvbSBcIkBtYXN0cmEvbWNwXCI7XG5pbXBvcnQge1xuICBHUkFQSElUSV9DT05GSUdfU0NIRU1BLFxuICByZXNvbHZlR3JhcGhpdGlDb25maWcsXG4gIHR5cGUgR3JhcGhpdGlQbHVnaW5Db25maWcsXG4gIHR5cGUgUmVzb2x2ZWRHcmFwaGl0aVBsdWdpbkNvbmZpZyxcbn0gZnJvbSBcIi4vY29uZmlnLnRzXCI7XG5cbmNvbnN0IExBS0VCQVNFX01BTklGRVNUID0gYXBwa2l0UGx1Z2luLmRhdGEobGFrZWJhc2UpLnBsdWdpbi5tYW5pZmVzdDtcblxuaW50ZXJmYWNlIFRvb2xraXRFbnRyeSB7XG4gIHBsdWdpbk5hbWU6IHN0cmluZztcbiAgbG9jYWxOYW1lOiBzdHJpbmc7XG4gIGRlZjoge1xuICAgIG5hbWU6IHN0cmluZztcbiAgICBkZXNjcmlwdGlvbjogc3RyaW5nO1xuICAgIHBhcmFtZXRlcnM6IHVua25vd247XG4gIH07XG59XG5cbmV4cG9ydCBjbGFzcyBHcmFwaGl0aVBsdWdpbiBleHRlbmRzIFBsdWdpbjxHcmFwaGl0aVBsdWdpbkNvbmZpZz4ge1xuICBzdGF0aWMgbWFuaWZlc3Q6IFBsdWdpbk1hbmlmZXN0PFwiZ3JhcGhpdGlcIj4gPSB7XG4gICAgbmFtZTogXCJncmFwaGl0aVwiLFxuICAgIGRpc3BsYXlOYW1lOiBcIkdyYXBoaXRpXCIsXG4gICAgZGVzY3JpcHRpb246XG4gICAgICBcIlJ1bnMgdGhlIGRieC10b29scyBHcmFwaGl0aSBNQ1Agc2lkZWNhciB3aXRoIExha2ViYXNlLWJhY2tlZCByZWNvdmVyeSBhbmQgXCIgK1xuICAgICAgXCJwdWJsaXNoZXMgaXQgdGhyb3VnaCB0aGUgQXBwJ3Mgc2luZ2xlIHBvcnQgd2l0aCBDYWRkeS5cIixcbiAgICBzdGFiaWxpdHk6IFwiYmV0YVwiLFxuICAgIHJlc291cmNlczoge1xuICAgICAgcmVxdWlyZWQ6IFtdLFxuICAgICAgb3B0aW9uYWw6IFsuLi5MQUtFQkFTRV9NQU5JRkVTVC5yZXNvdXJjZXMucmVxdWlyZWRdLFxuICAgIH0sXG4gICAgY29uZmlnOiB7IHNjaGVtYTogR1JBUEhJVElfQ09ORklHX1NDSEVNQSB9LFxuICB9O1xuXG4gIHN0YXRpYyBnZXRSZXNvdXJjZVJlcXVpcmVtZW50cygpOiBSZXNvdXJjZVJlcXVpcmVtZW50W10ge1xuICAgIHJldHVybiBMQUtFQkFTRV9NQU5JRkVTVC5yZXNvdXJjZXMucmVxdWlyZWQubWFwKChyZXNvdXJjZSkgPT4gKHtcbiAgICAgIC4uLnJlc291cmNlLFxuICAgICAgcmVxdWlyZWQ6IHRydWUsXG4gICAgfSkpO1xuICB9XG5cbiAgcHJpdmF0ZSByZWFkb25seSBsb2dnZXIgPSBsb2cubG9nZ2VyKHRoaXMpO1xuICBwcml2YXRlIGNoaWxkcmVuOiBSZXR1cm5UeXBlPHR5cGVvZiBleGVjLnNwYXduPltdID0gW107XG4gIHByaXZhdGUgbWNwPzogTUNQQ2xpZW50O1xuICBwcml2YXRlIG1jcFRvb2xzOiBSZWNvcmQ8c3RyaW5nLCBUb29sPiA9IHt9O1xuICBwcml2YXRlIHJlc29sdmVkPzogUmVzb2x2ZWRHcmFwaGl0aVBsdWdpbkNvbmZpZztcbiAgcHJpdmF0ZSBzdG9wcGluZyA9IGZhbHNlO1xuXG4gIG92ZXJyaWRlIGFzeW5jIHNldHVwKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IGNvbmZpZ3VyZWQgPSByZXNvbHZlR3JhcGhpdGlDb25maWcodGhpcy5jb25maWcpO1xuICAgIHRoaXMucmVzb2x2ZWQgPSB7XG4gICAgICAuLi5jb25maWd1cmVkLFxuICAgICAgZ3JhcGhpdGlQb3J0OiBjb25maWd1cmVkLmdyYXBoaXRpUG9ydCB8fCAoYXdhaXQgYXZhaWxhYmxlUG9ydCgpKSxcbiAgICB9O1xuICAgIGlmICghcHJvY2Vzcy5lbnYuTEFLRUJBU0VfRU5EUE9JTlQgJiYgIXByb2Nlc3MuZW52LlBHSE9TVCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICBcIkdyYXBoaXRpIHJlcXVpcmVzIExha2ViYXNlOyByZWdpc3RlciBsYWtlYmFzZSgpIGJlZm9yZSBncmFwaGl0aSgpIGFuZCBiaW5kIGl0cyBQb3N0Z3JlcyByZXNvdXJjZVwiLFxuICAgICAgKTtcbiAgICB9XG4gICAgZm9yIChjb25zdCBzaWduYWwgb2YgW1wiU0lHSU5UXCIsIFwiU0lHVEVSTVwiLCBcIlNJR0hVUFwiXSBhcyBjb25zdCkge1xuICAgICAgcHJvY2Vzcy5vbmNlKHNpZ25hbCwgKCkgPT4gdGhpcy5zdG9wR3JhcGhpdGkoKSk7XG4gICAgfVxuICAgIGNvbnN0IHN1cGVydmlzb3IgPSBhcHBraXRJbnRlcmNlcHRvci5jcmVhdGVJbnRlcmNlcHRvckNvbnRleHQoe30pO1xuICAgIHN1cGVydmlzb3IuY29udGV4dC5vblRlYXJkb3duKCgpID0+IHRoaXMuc3RvcEdyYXBoaXRpKCkpO1xuICAgIGNvbnN0IGdyYXBoaXRpID0gZXhlYy5zcGF3bih0aGlzLnJlc29sdmVkLnB5dGhvbiwgW1wiLW1cIiwgXCJkYnhfdG9vbHMuZ3JhcGhpdGlcIiwgXCJzdGFydFwiXSwge1xuICAgICAgZW52OiB7XG4gICAgICAgIC4uLnByb2Nlc3MuZW52LFxuICAgICAgICBHUkFQSElUSV9IT1NUOiBcIjEyNy4wLjAuMVwiLFxuICAgICAgICBHUkFQSElUSV9QT1JUOiBTdHJpbmcodGhpcy5yZXNvbHZlZC5ncmFwaGl0aVBvcnQpLFxuICAgICAgICBKT1VSTkFMX05BTUVTUEFDRTogdGhpcy5yZXNvbHZlZC5qb3VybmFsTmFtZXNwYWNlLFxuICAgICAgfSxcbiAgICAgIHN0ZGluOiBcImlnbm9yZVwiLFxuICAgICAgc3Rkb3V0OiBcImluaGVyaXRcIixcbiAgICAgIHN0ZGVycjogXCJpbmhlcml0XCIsXG4gICAgfSk7XG4gICAgY29uc3QgY2FkZHkgPSBleGVjLnNwYXduKFxuICAgICAgdGhpcy5yZXNvbHZlZC5weXRob24sXG4gICAgICBbXG4gICAgICAgIFwiLW1cIixcbiAgICAgICAgXCJkYnhfdG9vbHMuZ3JhcGhpdGkucHJveHlcIixcbiAgICAgICAgXCItLXB1YmxpYy1wb3J0XCIsXG4gICAgICAgIFN0cmluZyh0aGlzLnJlc29sdmVkLnB1YmxpY1BvcnQpLFxuICAgICAgICBcIi0tYXBwLXBvcnRcIixcbiAgICAgICAgU3RyaW5nKHRoaXMucmVzb2x2ZWQuYXBwUG9ydCksXG4gICAgICAgIFwiLS1ncmFwaGl0aS1wb3J0XCIsXG4gICAgICAgIFN0cmluZyh0aGlzLnJlc29sdmVkLmdyYXBoaXRpUG9ydCksXG4gICAgICAgIFwiLS1yb3V0ZS1wcmVmaXhcIixcbiAgICAgICAgdGhpcy5yZXNvbHZlZC5yb3V0ZVByZWZpeCxcbiAgICAgIF0sXG4gICAgICB7XG4gICAgICAgIGVudjogcHJvY2Vzcy5lbnYsXG4gICAgICAgIHN0ZGluOiBcImlnbm9yZVwiLFxuICAgICAgICBzdGRvdXQ6IFwiaW5oZXJpdFwiLFxuICAgICAgICBzdGRlcnI6IFwiaW5oZXJpdFwiLFxuICAgICAgfSxcbiAgICApO1xuICAgIHRoaXMuY2hpbGRyZW4gPSBbZ3JhcGhpdGksIGNhZGR5XTtcbiAgICBmb3IgKGNvbnN0IGNoaWxkIG9mIHRoaXMuY2hpbGRyZW4pIHN1cGVydmlzb3IuY29udGV4dC5iaW5kUHJvY2VzcyhjaGlsZCk7XG4gICAgYXdhaXQgd2FpdEZvckdyYXBoaXRpKHRoaXMucmVzb2x2ZWQuZ3JhcGhpdGlQb3J0KTtcbiAgICB0aGlzLm1jcCA9IG5ldyBNQ1BDbGllbnQoe1xuICAgICAgaWQ6IGBhcHBraXQtZ3JhcGhpdGktJHt0aGlzLnJlc29sdmVkLmdyYXBoaXRpUG9ydH1gLFxuICAgICAgc2VydmVyczoge1xuICAgICAgICBncmFwaGl0aToge1xuICAgICAgICAgIHVybDogbmV3IFVSTChgaHR0cDovLzEyNy4wLjAuMToke3RoaXMucmVzb2x2ZWQuZ3JhcGhpdGlQb3J0fS9tY3BgKSxcbiAgICAgICAgfSxcbiAgICAgIH0sXG4gICAgfSk7XG4gICAgY29uc3QgZGlzY292ZXJlZCA9IGF3YWl0IHRoaXMubWNwLmxpc3RUb29scygpO1xuICAgIHRoaXMubWNwVG9vbHMgPSBPYmplY3QuZnJvbUVudHJpZXMoXG4gICAgICBPYmplY3QuZW50cmllcyhkaXNjb3ZlcmVkKS5tYXAoKFtuYW1lLCB0b29sXSkgPT4gW1xuICAgICAgICBuYW1lLnJlcGxhY2UoL15ncmFwaGl0aV8vLCBcIlwiKSxcbiAgICAgICAgdG9vbCBhcyBUb29sLFxuICAgICAgXSksXG4gICAgKTtcbiAgICB0aGlzLmxvZ2dlci5pbmZvKFwicmVhZHlcIiwge1xuICAgICAgYXBwUG9ydDogdGhpcy5yZXNvbHZlZC5hcHBQb3J0LFxuICAgICAgZ3JhcGhpdGlQb3J0OiB0aGlzLnJlc29sdmVkLmdyYXBoaXRpUG9ydCxcbiAgICAgIHB1YmxpY1BvcnQ6IHRoaXMucmVzb2x2ZWQucHVibGljUG9ydCxcbiAgICAgIG1jcFBhdGg6IGAke3RoaXMucmVzb2x2ZWQucm91dGVQcmVmaXh9L21jcC9gLFxuICAgICAgdG9vbHM6IE9iamVjdC5rZXlzKHRoaXMubWNwVG9vbHMpLFxuICAgIH0pO1xuICB9XG5cbiAgb3ZlcnJpZGUgYWJvcnRBY3RpdmVPcGVyYXRpb25zKCk6IHZvaWQge1xuICAgIHN1cGVyLmFib3J0QWN0aXZlT3BlcmF0aW9ucygpO1xuICAgIHRoaXMuc3RvcEdyYXBoaXRpKCk7XG4gICAgdm9pZCB0aGlzLm1jcD8uZGlzY29ubmVjdCgpO1xuICAgIHRoaXMubWNwID0gdW5kZWZpbmVkO1xuICAgIHRoaXMubWNwVG9vbHMgPSB7fTtcbiAgICBmb3IgKGNvbnN0IGNoaWxkIG9mIHRoaXMuY2hpbGRyZW4pIHtcbiAgICAgIGlmICghY2hpbGQua2lsbGVkKSBjaGlsZC5raWxsKFwiU0lHVEVSTVwiKTtcbiAgICB9XG4gICAgdGhpcy5jaGlsZHJlbiA9IFtdO1xuICB9XG5cbiAgb3ZlcnJpZGUgZXhwb3J0cygpIHtcbiAgICBjb25zdCByZXNvbHZlZCA9IHRoaXMucmVzb2x2ZWQgPz8gcmVzb2x2ZUdyYXBoaXRpQ29uZmlnKHRoaXMuY29uZmlnKTtcbiAgICByZXR1cm4ge1xuICAgICAgYXBwUG9ydDogcmVzb2x2ZWQuYXBwUG9ydCxcbiAgICAgIGdyYXBoaXRpUG9ydDogcmVzb2x2ZWQuZ3JhcGhpdGlQb3J0LFxuICAgICAgbWNwUGF0aDogYCR7cmVzb2x2ZWQucm91dGVQcmVmaXh9L21jcC9gLFxuICAgICAgcHVibGljUG9ydDogcmVzb2x2ZWQucHVibGljUG9ydCxcbiAgICAgIHJvdXRlUHJlZml4OiByZXNvbHZlZC5yb3V0ZVByZWZpeCxcbiAgICB9O1xuICB9XG5cbiAgdG9vbGtpdCgpOiBSZWNvcmQ8c3RyaW5nLCBUb29sa2l0RW50cnk+IHtcbiAgICByZXR1cm4gT2JqZWN0LmZyb21FbnRyaWVzKFxuICAgICAgT2JqZWN0LmVudHJpZXModGhpcy5tY3BUb29scykubWFwKChbbmFtZSwgdG9vbF0pID0+IFtcbiAgICAgICAgbmFtZSxcbiAgICAgICAge1xuICAgICAgICAgIHBsdWdpbk5hbWU6IFwiZ3JhcGhpdGlcIixcbiAgICAgICAgICBsb2NhbE5hbWU6IG5hbWUsXG4gICAgICAgICAgZGVmOiB7XG4gICAgICAgICAgICBuYW1lLFxuICAgICAgICAgICAgZGVzY3JpcHRpb246IHRvb2wuZGVzY3JpcHRpb24sXG4gICAgICAgICAgICBwYXJhbWV0ZXJzOiB0b29sLmlucHV0U2NoZW1hID8/IHsgdHlwZTogXCJvYmplY3RcIiwgcHJvcGVydGllczoge30gfSxcbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgICAgXSksXG4gICAgKTtcbiAgfVxuXG4gIGFzeW5jIGV4ZWN1dGVBZ2VudFRvb2wobmFtZTogc3RyaW5nLCBhcmdzOiB1bmtub3duLCBzaWduYWw/OiBBYm9ydFNpZ25hbCk6IFByb21pc2U8dW5rbm93bj4ge1xuICAgIGNvbnN0IHRvb2wgPSB0aGlzLm1jcFRvb2xzW25hbWVdO1xuICAgIGlmICghdG9vbD8uZXhlY3V0ZSkgdGhyb3cgbmV3IEVycm9yKGBVbmtub3duIEdyYXBoaXRpIHRvb2w6ICR7bmFtZX1gKTtcbiAgICByZXR1cm4gdG9vbC5leGVjdXRlKGFyZ3MsIHsgYWJvcnRTaWduYWw6IHNpZ25hbCB9IGFzIG5ldmVyKTtcbiAgfVxuXG4gIHByaXZhdGUgc3RvcEdyYXBoaXRpKCk6IHZvaWQge1xuICAgIGlmICh0aGlzLnN0b3BwaW5nIHx8ICF0aGlzLnJlc29sdmVkKSByZXR1cm47XG4gICAgdGhpcy5zdG9wcGluZyA9IHRydWU7XG4gICAgdm9pZCBleGVjLnNwYXduKHRoaXMucmVzb2x2ZWQucHl0aG9uLCBbXCItbVwiLCBcImRieF90b29scy5ncmFwaGl0aVwiLCBcImRvd25cIl0sIHtcbiAgICAgIGVudjogcHJvY2Vzcy5lbnYsXG4gICAgICBzdGRpbjogXCJpZ25vcmVcIixcbiAgICAgIHN0ZG91dDogXCJpbmhlcml0XCIsXG4gICAgICBzdGRlcnI6IFwiaW5oZXJpdFwiLFxuICAgIH0pO1xuICB9XG59XG5cbmV4cG9ydCBjb25zdCBncmFwaGl0aSA9IHRvUGx1Z2luKEdyYXBoaXRpUGx1Z2luKTtcblxuYXN5bmMgZnVuY3Rpb24gd2FpdEZvckdyYXBoaXRpKHBvcnQ6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICBmb3IgYXdhaXQgKGNvbnN0IHJlYWR5IG9mIGFzeW5jTW9kdWxlLnBvbGwoXG4gICAgYXN5bmMgKHsgc2lnbmFsIH0pID0+IHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2goYGh0dHA6Ly8xMjcuMC4wLjE6JHtwb3J0fS9oZWFsdGhgLCB7IHNpZ25hbCB9KTtcbiAgICAgICAgcmV0dXJuIHJlc3BvbnNlLm9rO1xuICAgICAgfSBjYXRjaCB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICB9LFxuICAgIHtcbiAgICAgIGludGVydmFsTXM6IDI1MCxcbiAgICAgIHRpbWVvdXRNczogNjBfMDAwLFxuICAgICAgcHJlZGljYXRlOiAocmVhZHkpID0+ICFyZWFkeSxcbiAgICB9LFxuICApKSB7XG4gICAgaWYgKHJlYWR5KSByZXR1cm47XG4gIH1cbn1cblxuYXN5bmMgZnVuY3Rpb24gYXZhaWxhYmxlUG9ydCgpOiBQcm9taXNlPG51bWJlcj4ge1xuICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgIGNvbnN0IHNlcnZlciA9IGNyZWF0ZVNlcnZlcigpO1xuICAgIHNlcnZlci5vbmNlKFwiZXJyb3JcIiwgcmVqZWN0KTtcbiAgICBzZXJ2ZXIubGlzdGVuKDAsIFwiMTI3LjAuMC4xXCIsICgpID0+IHtcbiAgICAgIGNvbnN0IGFkZHJlc3MgPSBzZXJ2ZXIuYWRkcmVzcygpO1xuICAgICAgaWYgKCFhZGRyZXNzIHx8IHR5cGVvZiBhZGRyZXNzID09PSBcInN0cmluZ1wiKSB7XG4gICAgICAgIHNlcnZlci5jbG9zZSgpO1xuICAgICAgICByZWplY3QobmV3IEVycm9yKFwiQ291bGQgbm90IGFsbG9jYXRlIGEgR3JhcGhpdGkgbG9vcGJhY2sgcG9ydFwiKSk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIHNlcnZlci5jbG9zZSgoZXJyb3IpID0+IChlcnJvciA/IHJlamVjdChlcnJvcikgOiByZXNvbHZlKGFkZHJlc3MucG9ydCkpKTtcbiAgICB9KTtcbiAgfSk7XG59XG4iXX0=
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../src/config.ts","../src/plugin.ts","../index.ts"],"version":"5.9.3"}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dbx-tools/appkit-graphiti",
|
|
3
|
+
"repository": {
|
|
4
|
+
"type": "git",
|
|
5
|
+
"url": "git+https://github.com/reggie-db/dbx-tools.git",
|
|
6
|
+
"directory": "packages/js/node/appkit-graphiti"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "projen build",
|
|
10
|
+
"compile": "projen compile",
|
|
11
|
+
"default": "projen default",
|
|
12
|
+
"package": "projen package",
|
|
13
|
+
"post-compile": "projen post-compile",
|
|
14
|
+
"pre-compile": "projen pre-compile",
|
|
15
|
+
"prepack": "projen prepack",
|
|
16
|
+
"test": "projen test",
|
|
17
|
+
"watch": "projen watch",
|
|
18
|
+
"projen": "projen"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/bun": "^1.3.14",
|
|
22
|
+
"@types/json-schema": "^7",
|
|
23
|
+
"@types/node": "^24.6.0",
|
|
24
|
+
"typescript": "^5.9.3"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@databricks/appkit": "^0.60.0",
|
|
28
|
+
"@dbx-tools/appkit": "0.6.113",
|
|
29
|
+
"@dbx-tools/core": "0.6.113",
|
|
30
|
+
"@dbx-tools/shared-core": "0.6.113",
|
|
31
|
+
"@mastra/core": "1.47.0",
|
|
32
|
+
"@mastra/mcp": "1.12.0"
|
|
33
|
+
},
|
|
34
|
+
"main": "./lib/index.js",
|
|
35
|
+
"license": "UNLICENSED",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public",
|
|
38
|
+
"main": "./lib/index.js",
|
|
39
|
+
"types": "./lib/index.d.ts",
|
|
40
|
+
"exports": {
|
|
41
|
+
".": {
|
|
42
|
+
"types": "./lib/index.d.ts",
|
|
43
|
+
"default": "./lib/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./package.json": "./package.json"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"version": "0.6.113",
|
|
49
|
+
"types": "./lib/index.d.ts",
|
|
50
|
+
"type": "module",
|
|
51
|
+
"exports": {
|
|
52
|
+
".": {
|
|
53
|
+
"types": "./lib/index.d.ts",
|
|
54
|
+
"default": "./lib/index.js"
|
|
55
|
+
},
|
|
56
|
+
"./package.json": "./package.json"
|
|
57
|
+
},
|
|
58
|
+
"files": [
|
|
59
|
+
"index.ts",
|
|
60
|
+
"src",
|
|
61
|
+
"lib"
|
|
62
|
+
],
|
|
63
|
+
"dbxToolsConfig": {
|
|
64
|
+
"tags": [
|
|
65
|
+
"node"
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
"//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"bunx projen\"."
|
|
69
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppKit Graphiti sidecar ports, route prefix, and Python command.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { ConfigurationError, type BasePluginConfig } from "@databricks/appkit";
|
|
7
|
+
import { config as coreConfig } from "@dbx-tools/core";
|
|
8
|
+
import type { JSONSchema7 } from "json-schema";
|
|
9
|
+
|
|
10
|
+
export interface GraphitiPluginConfig extends BasePluginConfig {
|
|
11
|
+
publicPort?: number;
|
|
12
|
+
appPort?: number;
|
|
13
|
+
graphitiPort?: number;
|
|
14
|
+
routePrefix?: string;
|
|
15
|
+
python?: string;
|
|
16
|
+
journalNamespace?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ResolvedGraphitiPluginConfig extends GraphitiPluginConfig {
|
|
20
|
+
publicPort: number;
|
|
21
|
+
appPort: number;
|
|
22
|
+
graphitiPort: number;
|
|
23
|
+
routePrefix: string;
|
|
24
|
+
python: string;
|
|
25
|
+
journalNamespace: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const GRAPHITI_CONFIG_SCHEMA = {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {
|
|
31
|
+
publicPort: { type: "integer", minimum: 1, maximum: 65535 },
|
|
32
|
+
appPort: { type: "integer", minimum: 1, maximum: 65535 },
|
|
33
|
+
graphitiPort: { type: "integer", minimum: 1, maximum: 65535 },
|
|
34
|
+
routePrefix: { type: "string" },
|
|
35
|
+
python: { type: "string" },
|
|
36
|
+
journalNamespace: { type: "string" },
|
|
37
|
+
},
|
|
38
|
+
additionalProperties: false,
|
|
39
|
+
} satisfies JSONSchema7;
|
|
40
|
+
|
|
41
|
+
/** Resolve plugin config over exact environment values and stable defaults. */
|
|
42
|
+
export function resolveGraphitiConfig(
|
|
43
|
+
config: GraphitiPluginConfig = {},
|
|
44
|
+
): ResolvedGraphitiPluginConfig {
|
|
45
|
+
const publicPort = coreConfig.port(
|
|
46
|
+
config.publicPort,
|
|
47
|
+
"DATABRICKS_APP_PORT",
|
|
48
|
+
8000,
|
|
49
|
+
coreConfig.ENV_ONLY,
|
|
50
|
+
);
|
|
51
|
+
const appPort = coreConfig.port(
|
|
52
|
+
config.appPort,
|
|
53
|
+
"GRAPHITI_APP_PORT",
|
|
54
|
+
publicPort + 1,
|
|
55
|
+
coreConfig.ENV_ONLY,
|
|
56
|
+
);
|
|
57
|
+
const graphitiPort = coreConfig.port(
|
|
58
|
+
config.graphitiPort,
|
|
59
|
+
"GRAPHITI_PORT",
|
|
60
|
+
0,
|
|
61
|
+
coreConfig.ENV_ONLY,
|
|
62
|
+
);
|
|
63
|
+
if (new Set([publicPort, appPort, graphitiPort]).size !== 3) {
|
|
64
|
+
throw new ConfigurationError("Graphiti public, AppKit, and sidecar ports must be distinct");
|
|
65
|
+
}
|
|
66
|
+
const routePrefix = normalizeRoutePrefix(
|
|
67
|
+
config.routePrefix ??
|
|
68
|
+
coreConfig.text("GRAPHITI_ROUTE_PREFIX", coreConfig.ENV_ONLY) ??
|
|
69
|
+
"/graphiti",
|
|
70
|
+
);
|
|
71
|
+
const python =
|
|
72
|
+
config.python ?? coreConfig.text("PYTHON", coreConfig.ENV_ONLY)?.trim() ?? "python3";
|
|
73
|
+
const journalNamespace =
|
|
74
|
+
config.journalNamespace ??
|
|
75
|
+
coreConfig.text("JOURNAL_NAMESPACE", coreConfig.ENV_ONLY)?.trim() ??
|
|
76
|
+
process.env.DATABRICKS_APP_NAME?.trim() ??
|
|
77
|
+
"default";
|
|
78
|
+
return {
|
|
79
|
+
publicPort,
|
|
80
|
+
appPort,
|
|
81
|
+
graphitiPort,
|
|
82
|
+
routePrefix,
|
|
83
|
+
python,
|
|
84
|
+
journalNamespace,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Return the internal port an AppKit `server()` plugin should bind. */
|
|
89
|
+
export function appPort(config: GraphitiPluginConfig = {}): number {
|
|
90
|
+
return resolveGraphitiConfig(config).appPort;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function normalizeRoutePrefix(value: string): string {
|
|
94
|
+
const normalized = `/${value.trim().replace(/^\/+|\/+$/g, "")}`;
|
|
95
|
+
if (normalized === "/") {
|
|
96
|
+
throw new ConfigurationError("Graphiti routePrefix must contain a path segment");
|
|
97
|
+
}
|
|
98
|
+
return normalized;
|
|
99
|
+
}
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppKit plugin that supervises Graphiti and a Caddy single-port proxy.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { createServer } from "node:net";
|
|
7
|
+
import {
|
|
8
|
+
Plugin,
|
|
9
|
+
lakebase,
|
|
10
|
+
toPlugin,
|
|
11
|
+
type PluginManifest,
|
|
12
|
+
type ResourceRequirement,
|
|
13
|
+
} from "@databricks/appkit";
|
|
14
|
+
import { interceptor as appkitInterceptor, plugin as appkitPlugin } from "@dbx-tools/appkit";
|
|
15
|
+
import { exec } from "@dbx-tools/core";
|
|
16
|
+
import { async as asyncModule, log } from "@dbx-tools/shared-core";
|
|
17
|
+
import type { Tool } from "@mastra/core/tools";
|
|
18
|
+
import { MCPClient } from "@mastra/mcp";
|
|
19
|
+
import {
|
|
20
|
+
GRAPHITI_CONFIG_SCHEMA,
|
|
21
|
+
resolveGraphitiConfig,
|
|
22
|
+
type GraphitiPluginConfig,
|
|
23
|
+
type ResolvedGraphitiPluginConfig,
|
|
24
|
+
} from "./config.ts";
|
|
25
|
+
|
|
26
|
+
const LAKEBASE_MANIFEST = appkitPlugin.data(lakebase).plugin.manifest;
|
|
27
|
+
|
|
28
|
+
interface ToolkitEntry {
|
|
29
|
+
pluginName: string;
|
|
30
|
+
localName: string;
|
|
31
|
+
def: {
|
|
32
|
+
name: string;
|
|
33
|
+
description: string;
|
|
34
|
+
parameters: unknown;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class GraphitiPlugin extends Plugin<GraphitiPluginConfig> {
|
|
39
|
+
static manifest: PluginManifest<"graphiti"> = {
|
|
40
|
+
name: "graphiti",
|
|
41
|
+
displayName: "Graphiti",
|
|
42
|
+
description:
|
|
43
|
+
"Runs the dbx-tools Graphiti MCP sidecar with Lakebase-backed recovery and " +
|
|
44
|
+
"publishes it through the App's single port with Caddy.",
|
|
45
|
+
stability: "beta",
|
|
46
|
+
resources: {
|
|
47
|
+
required: [],
|
|
48
|
+
optional: [...LAKEBASE_MANIFEST.resources.required],
|
|
49
|
+
},
|
|
50
|
+
config: { schema: GRAPHITI_CONFIG_SCHEMA },
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
static getResourceRequirements(): ResourceRequirement[] {
|
|
54
|
+
return LAKEBASE_MANIFEST.resources.required.map((resource) => ({
|
|
55
|
+
...resource,
|
|
56
|
+
required: true,
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private readonly logger = log.logger(this);
|
|
61
|
+
private children: ReturnType<typeof exec.spawn>[] = [];
|
|
62
|
+
private mcp?: MCPClient;
|
|
63
|
+
private mcpTools: Record<string, Tool> = {};
|
|
64
|
+
private resolved?: ResolvedGraphitiPluginConfig;
|
|
65
|
+
private stopping = false;
|
|
66
|
+
|
|
67
|
+
override async setup(): Promise<void> {
|
|
68
|
+
const configured = resolveGraphitiConfig(this.config);
|
|
69
|
+
this.resolved = {
|
|
70
|
+
...configured,
|
|
71
|
+
graphitiPort: configured.graphitiPort || (await availablePort()),
|
|
72
|
+
};
|
|
73
|
+
if (!process.env.LAKEBASE_ENDPOINT && !process.env.PGHOST) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
"Graphiti requires Lakebase; register lakebase() before graphiti() and bind its Postgres resource",
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"] as const) {
|
|
79
|
+
process.once(signal, () => this.stopGraphiti());
|
|
80
|
+
}
|
|
81
|
+
const supervisor = appkitInterceptor.createInterceptorContext({});
|
|
82
|
+
supervisor.context.onTeardown(() => this.stopGraphiti());
|
|
83
|
+
const graphiti = exec.spawn(this.resolved.python, ["-m", "dbx_tools.graphiti", "start"], {
|
|
84
|
+
env: {
|
|
85
|
+
...process.env,
|
|
86
|
+
GRAPHITI_HOST: "127.0.0.1",
|
|
87
|
+
GRAPHITI_PORT: String(this.resolved.graphitiPort),
|
|
88
|
+
JOURNAL_NAMESPACE: this.resolved.journalNamespace,
|
|
89
|
+
},
|
|
90
|
+
stdin: "ignore",
|
|
91
|
+
stdout: "inherit",
|
|
92
|
+
stderr: "inherit",
|
|
93
|
+
});
|
|
94
|
+
const caddy = exec.spawn(
|
|
95
|
+
this.resolved.python,
|
|
96
|
+
[
|
|
97
|
+
"-m",
|
|
98
|
+
"dbx_tools.graphiti.proxy",
|
|
99
|
+
"--public-port",
|
|
100
|
+
String(this.resolved.publicPort),
|
|
101
|
+
"--app-port",
|
|
102
|
+
String(this.resolved.appPort),
|
|
103
|
+
"--graphiti-port",
|
|
104
|
+
String(this.resolved.graphitiPort),
|
|
105
|
+
"--route-prefix",
|
|
106
|
+
this.resolved.routePrefix,
|
|
107
|
+
],
|
|
108
|
+
{
|
|
109
|
+
env: process.env,
|
|
110
|
+
stdin: "ignore",
|
|
111
|
+
stdout: "inherit",
|
|
112
|
+
stderr: "inherit",
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
this.children = [graphiti, caddy];
|
|
116
|
+
for (const child of this.children) supervisor.context.bindProcess(child);
|
|
117
|
+
await waitForGraphiti(this.resolved.graphitiPort);
|
|
118
|
+
this.mcp = new MCPClient({
|
|
119
|
+
id: `appkit-graphiti-${this.resolved.graphitiPort}`,
|
|
120
|
+
servers: {
|
|
121
|
+
graphiti: {
|
|
122
|
+
url: new URL(`http://127.0.0.1:${this.resolved.graphitiPort}/mcp`),
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
const discovered = await this.mcp.listTools();
|
|
127
|
+
this.mcpTools = Object.fromEntries(
|
|
128
|
+
Object.entries(discovered).map(([name, tool]) => [
|
|
129
|
+
name.replace(/^graphiti_/, ""),
|
|
130
|
+
tool as Tool,
|
|
131
|
+
]),
|
|
132
|
+
);
|
|
133
|
+
this.logger.info("ready", {
|
|
134
|
+
appPort: this.resolved.appPort,
|
|
135
|
+
graphitiPort: this.resolved.graphitiPort,
|
|
136
|
+
publicPort: this.resolved.publicPort,
|
|
137
|
+
mcpPath: `${this.resolved.routePrefix}/mcp/`,
|
|
138
|
+
tools: Object.keys(this.mcpTools),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
override abortActiveOperations(): void {
|
|
143
|
+
super.abortActiveOperations();
|
|
144
|
+
this.stopGraphiti();
|
|
145
|
+
void this.mcp?.disconnect();
|
|
146
|
+
this.mcp = undefined;
|
|
147
|
+
this.mcpTools = {};
|
|
148
|
+
for (const child of this.children) {
|
|
149
|
+
if (!child.killed) child.kill("SIGTERM");
|
|
150
|
+
}
|
|
151
|
+
this.children = [];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
override exports() {
|
|
155
|
+
const resolved = this.resolved ?? resolveGraphitiConfig(this.config);
|
|
156
|
+
return {
|
|
157
|
+
appPort: resolved.appPort,
|
|
158
|
+
graphitiPort: resolved.graphitiPort,
|
|
159
|
+
mcpPath: `${resolved.routePrefix}/mcp/`,
|
|
160
|
+
publicPort: resolved.publicPort,
|
|
161
|
+
routePrefix: resolved.routePrefix,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
toolkit(): Record<string, ToolkitEntry> {
|
|
166
|
+
return Object.fromEntries(
|
|
167
|
+
Object.entries(this.mcpTools).map(([name, tool]) => [
|
|
168
|
+
name,
|
|
169
|
+
{
|
|
170
|
+
pluginName: "graphiti",
|
|
171
|
+
localName: name,
|
|
172
|
+
def: {
|
|
173
|
+
name,
|
|
174
|
+
description: tool.description,
|
|
175
|
+
parameters: tool.inputSchema ?? { type: "object", properties: {} },
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
]),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async executeAgentTool(name: string, args: unknown, signal?: AbortSignal): Promise<unknown> {
|
|
183
|
+
const tool = this.mcpTools[name];
|
|
184
|
+
if (!tool?.execute) throw new Error(`Unknown Graphiti tool: ${name}`);
|
|
185
|
+
return tool.execute(args, { abortSignal: signal } as never);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private stopGraphiti(): void {
|
|
189
|
+
if (this.stopping || !this.resolved) return;
|
|
190
|
+
this.stopping = true;
|
|
191
|
+
void exec.spawn(this.resolved.python, ["-m", "dbx_tools.graphiti", "down"], {
|
|
192
|
+
env: process.env,
|
|
193
|
+
stdin: "ignore",
|
|
194
|
+
stdout: "inherit",
|
|
195
|
+
stderr: "inherit",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export const graphiti = toPlugin(GraphitiPlugin);
|
|
201
|
+
|
|
202
|
+
async function waitForGraphiti(port: number): Promise<void> {
|
|
203
|
+
for await (const ready of asyncModule.poll(
|
|
204
|
+
async ({ signal }) => {
|
|
205
|
+
try {
|
|
206
|
+
const response = await fetch(`http://127.0.0.1:${port}/health`, { signal });
|
|
207
|
+
return response.ok;
|
|
208
|
+
} catch {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
intervalMs: 250,
|
|
214
|
+
timeoutMs: 60_000,
|
|
215
|
+
predicate: (ready) => !ready,
|
|
216
|
+
},
|
|
217
|
+
)) {
|
|
218
|
+
if (ready) return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function availablePort(): Promise<number> {
|
|
223
|
+
return new Promise((resolve, reject) => {
|
|
224
|
+
const server = createServer();
|
|
225
|
+
server.once("error", reject);
|
|
226
|
+
server.listen(0, "127.0.0.1", () => {
|
|
227
|
+
const address = server.address();
|
|
228
|
+
if (!address || typeof address === "string") {
|
|
229
|
+
server.close();
|
|
230
|
+
reject(new Error("Could not allocate a Graphiti loopback port"));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
server.close((error) => (error ? reject(error) : resolve(address.port)));
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
}
|