@dbx-tools/appkit-memory 0.0.0-stage → 0.9.131
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +78 -2
- package/index.ts +17 -0
- package/lib/index.d.ts +13 -0
- package/lib/index.js +14 -0
- package/lib/smoke/mastra-tool.d.ts +1 -0
- package/lib/smoke/mastra-tool.js +46 -0
- package/lib/src/_contracts.d.ts +71 -0
- package/lib/src/_contracts.js +63 -0
- package/lib/src/_mastra.d.ts +11 -0
- package/lib/src/_mastra.js +66 -0
- package/lib/src/config.d.ts +70 -0
- package/lib/src/config.js +87 -0
- package/lib/src/databricks/_runtime.d.ts +18 -0
- package/lib/src/databricks/_runtime.js +79 -0
- package/lib/src/databricks/client.d.ts +48 -0
- package/lib/src/databricks/client.js +174 -0
- package/lib/src/databricks/index.d.ts +3 -0
- package/lib/src/databricks/index.js +6 -0
- package/lib/src/graphiti/_backend.d.ts +9 -0
- package/lib/src/graphiti/_backend.js +214 -0
- package/lib/src/graphiti/index.d.ts +3 -0
- package/lib/src/graphiti/index.js +6 -0
- package/lib/src/graphiti/options.d.ts +7 -0
- package/lib/src/graphiti/options.js +7 -0
- package/lib/src/graphiti/runtime.d.ts +49 -0
- package/lib/src/graphiti/runtime.js +170 -0
- package/lib/src/plugin.d.ts +38 -0
- package/lib/src/plugin.js +196 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +104 -4
- package/src/_contracts.ts +107 -0
- package/src/_mastra.ts +80 -0
- package/src/config.ts +134 -0
- package/src/databricks/_runtime.ts +103 -0
- package/src/databricks/client.ts +251 -0
- package/src/databricks/index.ts +7 -0
- package/src/graphiti/_backend.ts +282 -0
- package/src/graphiti/index.ts +7 -0
- package/src/graphiti/options.ts +17 -0
- package/src/graphiti/runtime.ts +261 -0
- package/src/plugin.ts +254 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request-scoped Databricks managed-memory routing.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getExecutionContext, type WorkspaceClient } from "@databricks/appkit";
|
|
8
|
+
import { createCache, getOrSet } from "@dbx-tools/cache/cache";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
RecallMemoryInputSchema,
|
|
12
|
+
type RecallMemoryInput,
|
|
13
|
+
type RecallMemoryResult,
|
|
14
|
+
type RememberMemoryInput,
|
|
15
|
+
type RememberMemoryResult,
|
|
16
|
+
} from "../_contracts.ts";
|
|
17
|
+
import type { ResolvedDatabricksMemoryOptions } from "../config.ts";
|
|
18
|
+
import { ManagedAgentMemoryClient } from "./client.ts";
|
|
19
|
+
|
|
20
|
+
const ACCESS_CACHE_TTL_MS = 5 * 60_000;
|
|
21
|
+
const accessCache = createCache({
|
|
22
|
+
maxEntries: 1_000,
|
|
23
|
+
ttl: ACCESS_CACHE_TTL_MS,
|
|
24
|
+
useClone: false,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/** Databricks backend that selects the active request credential for each call. */
|
|
28
|
+
export class DatabricksMemoryRuntime {
|
|
29
|
+
constructor(
|
|
30
|
+
private readonly workspaceClient: WorkspaceClient,
|
|
31
|
+
readonly options: ResolvedDatabricksMemoryOptions,
|
|
32
|
+
) {}
|
|
33
|
+
|
|
34
|
+
async available(actorId: string, signal?: AbortSignal): Promise<boolean> {
|
|
35
|
+
const request = this.requestClient(actorId);
|
|
36
|
+
return (
|
|
37
|
+
(await getOrSet(accessCache, request.cacheKey, () => request.client.available(signal))) ??
|
|
38
|
+
false
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async remember(
|
|
43
|
+
actorId: string,
|
|
44
|
+
input: RememberMemoryInput,
|
|
45
|
+
signal?: AbortSignal,
|
|
46
|
+
): Promise<RememberMemoryResult> {
|
|
47
|
+
const entry = await this.requestClient(actorId).client.saveMemory({
|
|
48
|
+
actorId,
|
|
49
|
+
...input,
|
|
50
|
+
...(signal ? { signal } : {}),
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
backend: "databricks",
|
|
54
|
+
memory: {
|
|
55
|
+
path: entry.path,
|
|
56
|
+
content: entry.content ?? input.content,
|
|
57
|
+
...(entry.description ? { description: entry.description } : {}),
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async recall(
|
|
63
|
+
actorId: string,
|
|
64
|
+
input: RecallMemoryInput,
|
|
65
|
+
signal?: AbortSignal,
|
|
66
|
+
): Promise<RecallMemoryResult> {
|
|
67
|
+
const parsed = RecallMemoryInputSchema.parse(input);
|
|
68
|
+
const results = await this.requestClient(actorId).client.searchMemory({
|
|
69
|
+
actorId,
|
|
70
|
+
query: parsed.query,
|
|
71
|
+
limit: parsed.limit,
|
|
72
|
+
...(signal ? { signal } : {}),
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
backend: "databricks",
|
|
76
|
+
memories: results.map(({ entry, score }) => ({
|
|
77
|
+
path: entry.path,
|
|
78
|
+
content: entry.content ?? "",
|
|
79
|
+
...(entry.description ? { description: entry.description } : {}),
|
|
80
|
+
...(score === undefined ? {} : { score }),
|
|
81
|
+
})),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private requestClient(actorId: string): {
|
|
86
|
+
client: ManagedAgentMemoryClient;
|
|
87
|
+
cacheKey: string;
|
|
88
|
+
} {
|
|
89
|
+
let workspaceClient = this.workspaceClient;
|
|
90
|
+
let credentialId = actorId;
|
|
91
|
+
try {
|
|
92
|
+
const execution = getExecutionContext();
|
|
93
|
+
workspaceClient = execution.client;
|
|
94
|
+
credentialId = "userId" in execution ? execution.userId : execution.serviceUserId;
|
|
95
|
+
} catch {
|
|
96
|
+
// Startup and tests have no active AppKit request context.
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
client: new ManagedAgentMemoryClient(workspaceClient, this.options),
|
|
100
|
+
cacheKey: `${this.options.memoryStore}:${credentialId}:${actorId}`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Databricks Agent Memory REST client.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { WorkspaceClient } from "@databricks/appkit";
|
|
8
|
+
import { databricks } from "@dbx-tools/appkit";
|
|
9
|
+
import { errorUtils } from "@dbx-tools/shared-core";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
|
|
12
|
+
import type { ResolvedDatabricksMemoryOptions } from "../config.ts";
|
|
13
|
+
|
|
14
|
+
export const ManagedMemoryEntrySchema = z
|
|
15
|
+
.object({
|
|
16
|
+
name: z.string().optional().describe("Managed memory entry resource name."),
|
|
17
|
+
actor_id: z.string().describe("Trusted application actor identifier."),
|
|
18
|
+
session_id: z.string().optional().describe("Associated session identifier."),
|
|
19
|
+
path: z.string().describe("Absolute memory path."),
|
|
20
|
+
content: z.string().optional().describe("Memory content."),
|
|
21
|
+
description: z.string().optional().describe("Memory retrieval description."),
|
|
22
|
+
source_type: z.string().optional().describe("Entry writer type."),
|
|
23
|
+
create_time: z.string().optional().describe("Entry creation timestamp."),
|
|
24
|
+
update_time: z.string().optional().describe("Entry update timestamp."),
|
|
25
|
+
})
|
|
26
|
+
.loose()
|
|
27
|
+
.describe("Databricks managed memory entry.");
|
|
28
|
+
|
|
29
|
+
export type ManagedMemoryEntry = z.infer<typeof ManagedMemoryEntrySchema>;
|
|
30
|
+
|
|
31
|
+
const SearchMemoryResponseSchema = z
|
|
32
|
+
.object({
|
|
33
|
+
managed_memory_entries: z
|
|
34
|
+
.array(ManagedMemoryEntrySchema)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Compatibility list of matching entries."),
|
|
37
|
+
results: z
|
|
38
|
+
.array(
|
|
39
|
+
z
|
|
40
|
+
.object({
|
|
41
|
+
managed_memory_entry: ManagedMemoryEntrySchema.describe("Matching memory entry."),
|
|
42
|
+
score: z.number().optional().describe("BM25 relevance score."),
|
|
43
|
+
})
|
|
44
|
+
.loose(),
|
|
45
|
+
)
|
|
46
|
+
.optional()
|
|
47
|
+
.describe("Ranked memory search results."),
|
|
48
|
+
})
|
|
49
|
+
.loose()
|
|
50
|
+
.describe("Databricks managed memory search response.");
|
|
51
|
+
|
|
52
|
+
const ListMemoryResponseSchema = z
|
|
53
|
+
.object({
|
|
54
|
+
managed_memory_entries: z
|
|
55
|
+
.array(ManagedMemoryEntrySchema)
|
|
56
|
+
.optional()
|
|
57
|
+
.describe("Managed memory entries in this page."),
|
|
58
|
+
next_page_token: z.string().optional().describe("Next page token."),
|
|
59
|
+
})
|
|
60
|
+
.loose()
|
|
61
|
+
.describe("Page of Databricks managed memory entries.");
|
|
62
|
+
|
|
63
|
+
/** One managed-memory search result with its optional backend score. */
|
|
64
|
+
export interface ManagedMemorySearchResult {
|
|
65
|
+
readonly entry: ManagedMemoryEntry;
|
|
66
|
+
readonly score?: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Thin REST client for actor-scoped Databricks Agent Memory operations. */
|
|
70
|
+
export class ManagedAgentMemoryClient {
|
|
71
|
+
readonly memoryStore: string;
|
|
72
|
+
|
|
73
|
+
constructor(
|
|
74
|
+
private readonly client: WorkspaceClient,
|
|
75
|
+
options: ResolvedDatabricksMemoryOptions,
|
|
76
|
+
) {
|
|
77
|
+
this.memoryStore = options.memoryStore;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async available(signal?: AbortSignal): Promise<boolean> {
|
|
81
|
+
try {
|
|
82
|
+
await this.client.apiClient.request(
|
|
83
|
+
{
|
|
84
|
+
path: `/api/2.0/agents/memory-stores/${encodeURIComponent(this.memoryStore)}`,
|
|
85
|
+
method: "GET",
|
|
86
|
+
headers: new Headers({ accept: "application/json" }),
|
|
87
|
+
raw: false,
|
|
88
|
+
},
|
|
89
|
+
signal ? databricks.toContext(signal) : undefined,
|
|
90
|
+
);
|
|
91
|
+
return true;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (errorUtils.errorContext(error).hasStatusCode(403, 404)) return false;
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async searchMemory(input: {
|
|
99
|
+
actorId: string;
|
|
100
|
+
query: string;
|
|
101
|
+
limit: number;
|
|
102
|
+
signal?: AbortSignal;
|
|
103
|
+
}): Promise<ManagedMemorySearchResult[]> {
|
|
104
|
+
const response = SearchMemoryResponseSchema.parse(
|
|
105
|
+
await this.client.apiClient.request(
|
|
106
|
+
{
|
|
107
|
+
path: `/api/2.0/agents/memory-stores/${encodeURIComponent(this.memoryStore)}/entries:search`,
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: new Headers({
|
|
110
|
+
accept: "application/json",
|
|
111
|
+
"content-type": "application/json",
|
|
112
|
+
}),
|
|
113
|
+
raw: false,
|
|
114
|
+
payload: {
|
|
115
|
+
actor_id: input.actorId,
|
|
116
|
+
query: input.query,
|
|
117
|
+
page_size: input.limit,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
input.signal ? databricks.toContext(input.signal) : undefined,
|
|
121
|
+
),
|
|
122
|
+
);
|
|
123
|
+
if (response.results) {
|
|
124
|
+
return response.results.map((result) => ({
|
|
125
|
+
entry: result.managed_memory_entry,
|
|
126
|
+
...(result.score === undefined ? {} : { score: result.score }),
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
return (response.managed_memory_entries ?? []).map((entry) => ({ entry }));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async saveMemory(input: {
|
|
133
|
+
actorId: string;
|
|
134
|
+
path: string;
|
|
135
|
+
content: string;
|
|
136
|
+
description?: string;
|
|
137
|
+
signal?: AbortSignal;
|
|
138
|
+
}): Promise<ManagedMemoryEntry> {
|
|
139
|
+
const existing = (await this.listMemoryEntries(input))[0];
|
|
140
|
+
if (existing?.name) {
|
|
141
|
+
return this.updateMemory(existing as ManagedMemoryEntry & { name: string }, input);
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
return await this.createMemory(input);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
if (!errorUtils.errorContext(error).hasStatusCode(409)) throw error;
|
|
147
|
+
const raced = (await this.listMemoryEntries(input))[0];
|
|
148
|
+
if (!raced?.name) throw error;
|
|
149
|
+
return this.updateMemory(raced as ManagedMemoryEntry & { name: string }, input);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private async createMemory(input: {
|
|
154
|
+
actorId: string;
|
|
155
|
+
path: string;
|
|
156
|
+
content: string;
|
|
157
|
+
description?: string;
|
|
158
|
+
signal?: AbortSignal;
|
|
159
|
+
}): Promise<ManagedMemoryEntry> {
|
|
160
|
+
return ManagedMemoryEntrySchema.parse(
|
|
161
|
+
await this.client.apiClient.request(
|
|
162
|
+
{
|
|
163
|
+
path: `/api/2.0/agents/memory-stores/${encodeURIComponent(this.memoryStore)}/entries`,
|
|
164
|
+
method: "POST",
|
|
165
|
+
headers: new Headers({
|
|
166
|
+
accept: "application/json",
|
|
167
|
+
"content-type": "application/json",
|
|
168
|
+
}),
|
|
169
|
+
raw: false,
|
|
170
|
+
payload: {
|
|
171
|
+
actor_id: input.actorId,
|
|
172
|
+
path: input.path,
|
|
173
|
+
content: input.content,
|
|
174
|
+
...(input.description ? { description: input.description } : {}),
|
|
175
|
+
source_type: "MANAGED_MEMORY_ENTRY_SOURCE_TYPE_AGENT",
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
input.signal ? databricks.toContext(input.signal) : undefined,
|
|
179
|
+
),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private async listMemoryEntries(input: {
|
|
184
|
+
actorId: string;
|
|
185
|
+
path: string;
|
|
186
|
+
signal?: AbortSignal;
|
|
187
|
+
}): Promise<ManagedMemoryEntry[]> {
|
|
188
|
+
const response = ListMemoryResponseSchema.parse(
|
|
189
|
+
await this.client.apiClient.request(
|
|
190
|
+
{
|
|
191
|
+
path: `/api/2.0/agents/memory-stores/${encodeURIComponent(this.memoryStore)}/entries`,
|
|
192
|
+
method: "GET",
|
|
193
|
+
headers: new Headers({ accept: "application/json" }),
|
|
194
|
+
query: {
|
|
195
|
+
actor_id: input.actorId,
|
|
196
|
+
path: input.path,
|
|
197
|
+
page_size: 100,
|
|
198
|
+
},
|
|
199
|
+
raw: false,
|
|
200
|
+
},
|
|
201
|
+
input.signal ? databricks.toContext(input.signal) : undefined,
|
|
202
|
+
),
|
|
203
|
+
);
|
|
204
|
+
return (response.managed_memory_entries ?? []).filter((entry) => entry.path === input.path);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private async updateMemory(
|
|
208
|
+
current: ManagedMemoryEntry & { name: string },
|
|
209
|
+
input: {
|
|
210
|
+
actorId: string;
|
|
211
|
+
path: string;
|
|
212
|
+
content: string;
|
|
213
|
+
description?: string;
|
|
214
|
+
signal?: AbortSignal;
|
|
215
|
+
},
|
|
216
|
+
): Promise<ManagedMemoryEntry> {
|
|
217
|
+
const headers = new Headers({
|
|
218
|
+
accept: "application/json",
|
|
219
|
+
"content-type": "application/json",
|
|
220
|
+
});
|
|
221
|
+
await this.client.config.authenticate(headers);
|
|
222
|
+
const response = await fetch(
|
|
223
|
+
new URL(
|
|
224
|
+
`/api/2.0/agents/${current.name
|
|
225
|
+
.split("/")
|
|
226
|
+
.map((part) => encodeURIComponent(part))
|
|
227
|
+
.join("/")}?update_mask=content,description`,
|
|
228
|
+
(await this.client.config.getHost()).toString(),
|
|
229
|
+
),
|
|
230
|
+
{
|
|
231
|
+
method: "PATCH",
|
|
232
|
+
headers,
|
|
233
|
+
body: JSON.stringify({
|
|
234
|
+
actor_id: input.actorId,
|
|
235
|
+
path: input.path,
|
|
236
|
+
content: input.content,
|
|
237
|
+
...(input.description ? { description: input.description } : {}),
|
|
238
|
+
}),
|
|
239
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
240
|
+
},
|
|
241
|
+
);
|
|
242
|
+
const body = await response.json().catch(() => undefined);
|
|
243
|
+
if (!response.ok) {
|
|
244
|
+
throw Object.assign(
|
|
245
|
+
new Error(`Databricks Agent Memory request failed (${response.status})`),
|
|
246
|
+
{ statusCode: response.status, response: body },
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
return ManagedMemoryEntrySchema.parse(body);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
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 * as client from "./client.ts";
|
|
6
|
+
export { ManagedMemoryEntrySchema, ManagedAgentMemoryClient } from "./client.ts";
|
|
7
|
+
export type { ManagedMemoryEntry, ManagedMemorySearchResult } from "./client.ts";
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy Graphiti implementation of the backend-neutral memory contract.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
8
|
+
import { createServer } from "node:net";
|
|
9
|
+
import { ConfigurationError } from "@databricks/appkit";
|
|
10
|
+
import { openApiTools, type OpenApiTool } from "@dbx-tools/appkit/openapi-tool";
|
|
11
|
+
import { profile as authProfile } from "@dbx-tools/auth";
|
|
12
|
+
import { configUtils } from "@dbx-tools/core";
|
|
13
|
+
import type { ChildProcessResult } from "@dbx-tools/core/exec";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
type GraphitiMemoryBackend,
|
|
18
|
+
type MemoryTurn,
|
|
19
|
+
type RecallMemoryInput,
|
|
20
|
+
type RecallMemoryResult,
|
|
21
|
+
type RememberMemoryInput,
|
|
22
|
+
type RememberMemoryResult,
|
|
23
|
+
} from "../_contracts.ts";
|
|
24
|
+
import {
|
|
25
|
+
graphitiOptionOverrides,
|
|
26
|
+
graphitiOptionsFromEnvironment,
|
|
27
|
+
resolveGraphitiOptions,
|
|
28
|
+
type GraphitiOptions,
|
|
29
|
+
type ResolvedGraphitiOptions,
|
|
30
|
+
} from "./options.ts";
|
|
31
|
+
import {
|
|
32
|
+
createGraphitiChildProcess,
|
|
33
|
+
graphitiHealthCheck,
|
|
34
|
+
graphitiHttpUrl,
|
|
35
|
+
graphitiRequestHeaders,
|
|
36
|
+
} from "./runtime.ts";
|
|
37
|
+
|
|
38
|
+
const GraphitiFactSchema = z
|
|
39
|
+
.object({
|
|
40
|
+
name: z.string().optional().describe("Graphiti fact name."),
|
|
41
|
+
fact: z.string().describe("Retrieved graph fact."),
|
|
42
|
+
})
|
|
43
|
+
.loose()
|
|
44
|
+
.describe("Graphiti fact search result.");
|
|
45
|
+
|
|
46
|
+
const GraphitiFactSearchSchema = z
|
|
47
|
+
.object({
|
|
48
|
+
facts: z.array(GraphitiFactSchema).optional().describe("Matching graph facts."),
|
|
49
|
+
error: z.string().optional().describe("Graphiti operation error."),
|
|
50
|
+
})
|
|
51
|
+
.loose()
|
|
52
|
+
.describe("Graphiti fact search response.");
|
|
53
|
+
|
|
54
|
+
const GraphitiWriteSchema = z
|
|
55
|
+
.object({
|
|
56
|
+
message: z.string().optional().describe("Graphiti write confirmation."),
|
|
57
|
+
error: z.string().optional().describe("Graphiti operation error."),
|
|
58
|
+
})
|
|
59
|
+
.loose()
|
|
60
|
+
.describe("Graphiti write response.");
|
|
61
|
+
|
|
62
|
+
class GraphitiBackend implements GraphitiMemoryBackend {
|
|
63
|
+
constructor(
|
|
64
|
+
private readonly options: ResolvedGraphitiOptions,
|
|
65
|
+
private readonly child: ChildProcessResult,
|
|
66
|
+
private readonly writeTool: OpenApiTool,
|
|
67
|
+
private readonly recallTool: OpenApiTool,
|
|
68
|
+
) {}
|
|
69
|
+
|
|
70
|
+
get running(): boolean {
|
|
71
|
+
return (
|
|
72
|
+
(this.child.exitCode === null || this.child.exitCode === undefined) &&
|
|
73
|
+
(this.child.signalCode === null || this.child.signalCode === undefined)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async remember(
|
|
78
|
+
actorId: string,
|
|
79
|
+
input: RememberMemoryInput,
|
|
80
|
+
signal?: AbortSignal,
|
|
81
|
+
): Promise<RememberMemoryResult> {
|
|
82
|
+
await this.write(
|
|
83
|
+
actorId,
|
|
84
|
+
{
|
|
85
|
+
name: input.path,
|
|
86
|
+
episode_body: input.content,
|
|
87
|
+
source: "text",
|
|
88
|
+
source_description: input.description ? `${input.path}: ${input.description}` : input.path,
|
|
89
|
+
},
|
|
90
|
+
signal,
|
|
91
|
+
);
|
|
92
|
+
return {
|
|
93
|
+
backend: "graphiti",
|
|
94
|
+
memory: {
|
|
95
|
+
path: input.path,
|
|
96
|
+
content: input.content,
|
|
97
|
+
...(input.description ? { description: input.description } : {}),
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async recall(
|
|
103
|
+
actorId: string,
|
|
104
|
+
input: RecallMemoryInput,
|
|
105
|
+
signal?: AbortSignal,
|
|
106
|
+
): Promise<RecallMemoryResult> {
|
|
107
|
+
const response = GraphitiFactSearchSchema.parse(
|
|
108
|
+
await this.request(
|
|
109
|
+
this.recallTool,
|
|
110
|
+
{
|
|
111
|
+
query: input.query,
|
|
112
|
+
max_facts: input.limit,
|
|
113
|
+
group_ids: [userScope(actorId)],
|
|
114
|
+
},
|
|
115
|
+
signal,
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
if (response.error) throw new Error(response.error);
|
|
119
|
+
return {
|
|
120
|
+
backend: "graphiti",
|
|
121
|
+
memories: (response.facts ?? []).map((fact) => ({
|
|
122
|
+
content: fact.fact,
|
|
123
|
+
...(fact.name ? { description: fact.name } : {}),
|
|
124
|
+
})),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async saveTurn(turn: MemoryTurn, signal?: AbortSignal): Promise<void> {
|
|
129
|
+
await this.write(
|
|
130
|
+
turn.actorId,
|
|
131
|
+
{
|
|
132
|
+
name: `Mastra turn ${turn.agentId}/${turn.threadId}`,
|
|
133
|
+
episode_body: `User: ${turn.user}\n\nAssistant: ${turn.assistant}`,
|
|
134
|
+
source: "message",
|
|
135
|
+
source_description: `Completed ${turn.agentId} conversation turn`,
|
|
136
|
+
},
|
|
137
|
+
signal,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
shutdown(): Promise<void> {
|
|
142
|
+
return this.child.shutdown();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private async write(
|
|
146
|
+
actorId: string,
|
|
147
|
+
args: Record<string, unknown>,
|
|
148
|
+
signal?: AbortSignal,
|
|
149
|
+
): Promise<void> {
|
|
150
|
+
const response = GraphitiWriteSchema.parse(
|
|
151
|
+
await this.request(this.writeTool, { ...args, group_id: userScope(actorId) }, signal),
|
|
152
|
+
);
|
|
153
|
+
if (response.error) throw new Error(response.error);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private async request(
|
|
157
|
+
tool: OpenApiTool,
|
|
158
|
+
args: Record<string, unknown>,
|
|
159
|
+
signal?: AbortSignal,
|
|
160
|
+
): Promise<unknown> {
|
|
161
|
+
if (!this.running) throw new Error("Graphiti sidecar is not running");
|
|
162
|
+
const response = await fetch(tool.url, {
|
|
163
|
+
method: tool.method,
|
|
164
|
+
headers: {
|
|
165
|
+
...graphitiRequestHeaders(this.options),
|
|
166
|
+
"content-type": "application/json",
|
|
167
|
+
},
|
|
168
|
+
body: JSON.stringify(args),
|
|
169
|
+
signal,
|
|
170
|
+
});
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Graphiti operation ${tool.id} failed with HTTP ${response.status}: ${await response.text()}`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return response.json();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Start a fully ready Graphiti backend for the first fallback operation. */
|
|
181
|
+
export async function startGraphitiBackend(
|
|
182
|
+
options: GraphitiOptions,
|
|
183
|
+
signal?: AbortSignal,
|
|
184
|
+
): Promise<GraphitiMemoryBackend> {
|
|
185
|
+
const configured = resolveGraphitiConfig(options);
|
|
186
|
+
if (configured.listen.scheme !== "tcp") {
|
|
187
|
+
throw new Error("AppKit memory requires a TCP Graphiti listener");
|
|
188
|
+
}
|
|
189
|
+
const [graphitiPort] = await distinctPorts(
|
|
190
|
+
configUtils.port(undefined, "DATABRICKS_APP_PORT", 8000, configUtils.ENV_ONLY),
|
|
191
|
+
configured.listen.port,
|
|
192
|
+
);
|
|
193
|
+
const resolved = resolveGraphitiOptions({
|
|
194
|
+
...configured,
|
|
195
|
+
bearer: randomBytes(32).toString("base64url"),
|
|
196
|
+
listen: { ...configured.listen, port: graphitiPort },
|
|
197
|
+
});
|
|
198
|
+
let tools: OpenApiTool[] | undefined;
|
|
199
|
+
const { child } = await createGraphitiChildProcess({
|
|
200
|
+
...resolved,
|
|
201
|
+
...(signal ? { signal } : {}),
|
|
202
|
+
healthCheck: async ({ signal }) => {
|
|
203
|
+
try {
|
|
204
|
+
if (!(await graphitiHealthCheck(resolved, signal))) return false;
|
|
205
|
+
const loaded = await openApiTools(graphitiHttpUrl(resolved, "/openapi.json"), {
|
|
206
|
+
signal,
|
|
207
|
+
headers: graphitiRequestHeaders(resolved),
|
|
208
|
+
});
|
|
209
|
+
tools = [
|
|
210
|
+
requiredOperation(loaded, "add_memory_sync"),
|
|
211
|
+
requiredOperation(loaded, "search_memory_facts"),
|
|
212
|
+
];
|
|
213
|
+
return true;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (signal.aborted) throw error;
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
if (!tools) {
|
|
221
|
+
await child.shutdown();
|
|
222
|
+
throw new Error("Graphiti OpenAPI tools were not captured during readiness");
|
|
223
|
+
}
|
|
224
|
+
return new GraphitiBackend(resolved, child, tools[0]!, tools[1]!);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function resolveGraphitiConfig(options: GraphitiOptions): ResolvedGraphitiOptions {
|
|
228
|
+
const environment = graphitiOptionsFromEnvironment(process.env);
|
|
229
|
+
if (environment.profile && !authProfile.resolveProfile({ profile: environment.profile })) {
|
|
230
|
+
environment.profile = undefined;
|
|
231
|
+
}
|
|
232
|
+
const overrides = graphitiOptionOverrides(options);
|
|
233
|
+
return resolveGraphitiOptions({
|
|
234
|
+
...environment,
|
|
235
|
+
...overrides,
|
|
236
|
+
listen: overrides.listen ??
|
|
237
|
+
environment.listen ?? {
|
|
238
|
+
scheme: "tcp",
|
|
239
|
+
host: "127.0.0.1",
|
|
240
|
+
port: 0,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function requiredOperation(tools: readonly OpenApiTool[], name: string): OpenApiTool {
|
|
246
|
+
const tool = tools.find(({ id }) => id === name);
|
|
247
|
+
if (!tool) throw new Error(`Graphiti OpenAPI is missing operation: ${name}`);
|
|
248
|
+
return tool;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function userScope(userId: string): string {
|
|
252
|
+
return `user_${createHash("sha256").update(userId).digest("base64url")}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function availablePort(): Promise<number> {
|
|
256
|
+
return new Promise((resolve, reject) => {
|
|
257
|
+
const server = createServer();
|
|
258
|
+
server.once("error", reject);
|
|
259
|
+
server.listen(0, "127.0.0.1", () => {
|
|
260
|
+
const address = server.address();
|
|
261
|
+
if (!address || typeof address === "string") {
|
|
262
|
+
server.close();
|
|
263
|
+
reject(new Error("Could not allocate a Graphiti loopback port"));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
server.close((error) => (error ? reject(error) : resolve(address.port)));
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function distinctPorts(appPort: number, graphitiPort: number): Promise<[number]> {
|
|
272
|
+
const ports = [appPort];
|
|
273
|
+
for (const configuredPort of [graphitiPort]) {
|
|
274
|
+
if (configuredPort && ports.includes(configuredPort)) {
|
|
275
|
+
throw new ConfigurationError("Graphiti sidecar ports must differ from DATABRICKS_APP_PORT");
|
|
276
|
+
}
|
|
277
|
+
let port = configuredPort || (await availablePort());
|
|
278
|
+
while (ports.includes(port)) port = await availablePort();
|
|
279
|
+
ports.push(port);
|
|
280
|
+
}
|
|
281
|
+
return ports.slice(1) as [number];
|
|
282
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
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 * as options from "./options.ts";
|
|
6
|
+
export * as runtime from "./runtime.ts";
|
|
7
|
+
export type { GraphitiHealthCheckContext, GraphitiHealthCheck, GraphitiChildProcess, GraphitiRuntimeOptions, GraphitiPythonCommand } from "./runtime.ts";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node export of the shared Graphiti option contract.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
GRAPHITI_DEFAULTS,
|
|
9
|
+
GraphitiCliOptionsSchema,
|
|
10
|
+
GraphitiOptionsSchema,
|
|
11
|
+
ResolvedGraphitiOptionsSchema,
|
|
12
|
+
graphitiOptionOverrides,
|
|
13
|
+
graphitiOptionsEnvironment,
|
|
14
|
+
graphitiOptionsFromEnvironment,
|
|
15
|
+
resolveGraphitiOptions,
|
|
16
|
+
} from "@dbx-tools/shared-graphiti";
|
|
17
|
+
export type { GraphitiOptions, ResolvedGraphitiOptions } from "@dbx-tools/shared-graphiti";
|