@pentatonic-ai/ai-agent-sdk 0.5.11 → 0.7.0
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 +345 -174
- package/bin/__tests__/callback-server.test.js +70 -0
- package/bin/__tests__/credentials.test.js +58 -0
- package/bin/__tests__/login.test.js +210 -0
- package/bin/__tests__/pkce.test.js +39 -0
- package/bin/__tests__/whoami.test.js +77 -0
- package/bin/cli.js +109 -440
- package/bin/commands/config.js +251 -0
- package/bin/commands/login.js +219 -0
- package/bin/commands/whoami.js +41 -0
- package/bin/lib/callback-server.js +137 -0
- package/bin/lib/credentials.js +100 -0
- package/bin/lib/pkce.js +26 -0
- package/package.json +4 -2
- package/packages/doctor/__tests__/detect.test.js +2 -6
- package/packages/doctor/src/checks/local-memory.js +164 -196
- package/packages/doctor/src/detect.js +11 -3
- package/packages/memory/src/__tests__/corpus-chunkers.test.js +143 -0
- package/packages/memory/src/__tests__/corpus-discover.test.js +175 -0
- package/packages/memory/src/__tests__/corpus-ingest.test.js +236 -0
- package/packages/memory/src/__tests__/corpus-signatures.test.js +175 -0
- package/packages/memory/src/__tests__/corpus-state.test.js +161 -0
- package/packages/memory/src/__tests__/ingest-corpus-opts.test.js +129 -0
- package/packages/memory/src/__tests__/search-kind.test.js +108 -0
- package/packages/memory/src/corpus/adapters.js +398 -0
- package/packages/memory/src/corpus/chunkers.js +328 -0
- package/packages/memory/src/corpus/cli.js +613 -0
- package/packages/memory/src/corpus/discover.js +379 -0
- package/packages/memory/src/corpus/index.js +68 -0
- package/packages/memory/src/corpus/ingest.js +356 -0
- package/packages/memory/src/corpus/signatures.js +280 -0
- package/packages/memory/src/corpus/state.js +134 -0
- package/packages/memory/src/index.js +18 -0
- package/packages/memory/src/ingest.js +20 -11
- package/packages/memory/src/openclaw/index.js +39 -1
- package/packages/memory/src/search.js +30 -7
- package/packages/memory-engine/.env.example +13 -0
- package/packages/memory-engine/README.md +131 -0
- package/packages/memory-engine/bench/README.md +99 -0
- package/packages/memory-engine/bench/scorecards-engine/agent-coding__pentatonic-baseline__20260427-142523.json +1115 -0
- package/packages/memory-engine/bench/scorecards-engine/chat-recall__pentatonic-baseline__20260427-142648.json +819 -0
- package/packages/memory-engine/bench/scorecards-engine/circular-economy__pentatonic-baseline__20260427-142757.json +1278 -0
- package/packages/memory-engine/bench/scorecards-engine/customer-support__pentatonic-baseline__20260427-142900.json +1018 -0
- package/packages/memory-engine/bench/scorecards-engine/marketplace-ops__pentatonic-baseline__20260427-142957.json +1038 -0
- package/packages/memory-engine/bench/scorecards-engine/product-catalogue__pentatonic-baseline__20260427-143122.json +961 -0
- package/packages/memory-engine/bench/scorecards-engine-via-docker/agent-coding__pentatonic-memory__20260427-161812.json +1115 -0
- package/packages/memory-engine/bench/scorecards-engine-via-docker/chat-recall__pentatonic-memory__20260427-161701.json +819 -0
- package/packages/memory-engine/bench/scorecards-engine-via-docker/circular-economy__pentatonic-memory__20260427-161713.json +1278 -0
- package/packages/memory-engine/bench/scorecards-engine-via-docker/customer-support__pentatonic-memory__20260427-161723.json +1018 -0
- package/packages/memory-engine/bench/scorecards-engine-via-docker/marketplace-ops__pentatonic-memory__20260427-161732.json +1038 -0
- package/packages/memory-engine/bench/scorecards-engine-via-docker/product-catalogue__pentatonic-memory__20260427-161741.json +937 -0
- package/packages/memory-engine/bench/scorecards-engine-via-l2-7-layer-populated/agent-coding__pentatonic-memory__20260427-184718.json +1115 -0
- package/packages/memory-engine/bench/scorecards-engine-via-l2-7-layer-populated/chat-recall__pentatonic-memory__20260427-184614.json +819 -0
- package/packages/memory-engine/bench/scorecards-engine-via-l2-7-layer-populated/circular-economy__pentatonic-memory__20260427-184809.json +1278 -0
- package/packages/memory-engine/bench/scorecards-engine-via-l2-7-layer-populated/customer-support__pentatonic-memory__20260427-184854.json +1018 -0
- package/packages/memory-engine/bench/scorecards-engine-via-l2-7-layer-populated/marketplace-ops__pentatonic-memory__20260427-184929.json +1038 -0
- package/packages/memory-engine/bench/scorecards-engine-via-l2-7-layer-populated/product-catalogue__pentatonic-memory__20260427-185015.json +961 -0
- package/packages/memory-engine/bench/scorecards-engine-via-l2-empty-layers/agent-coding__pentatonic-memory__20260427-175252.json +1115 -0
- package/packages/memory-engine/bench/scorecards-engine-via-l2-empty-layers/chat-recall__pentatonic-memory__20260427-175312.json +819 -0
- package/packages/memory-engine/bench/scorecards-engine-via-l2-empty-layers/circular-economy__pentatonic-memory__20260427-175335.json +1278 -0
- package/packages/memory-engine/bench/scorecards-engine-via-l2-empty-layers/customer-support__pentatonic-memory__20260427-175355.json +1018 -0
- package/packages/memory-engine/bench/scorecards-engine-via-l2-empty-layers/marketplace-ops__pentatonic-memory__20260427-175413.json +1038 -0
- package/packages/memory-engine/bench/scorecards-engine-via-l2-empty-layers/product-catalogue__pentatonic-memory__20260427-175430.json +883 -0
- package/packages/memory-engine/bench/scorecards-engine-via-shim/agent-coding__pentatonic-memory__20260427-155409.json +1115 -0
- package/packages/memory-engine/bench/scorecards-engine-via-shim/chat-recall__pentatonic-memory__20260427-155421.json +819 -0
- package/packages/memory-engine/bench/scorecards-engine-via-shim/circular-economy__pentatonic-memory__20260427-155433.json +1278 -0
- package/packages/memory-engine/bench/scorecards-engine-via-shim/customer-support__pentatonic-memory__20260427-155443.json +1018 -0
- package/packages/memory-engine/bench/scorecards-engine-via-shim/marketplace-ops__pentatonic-memory__20260427-155453.json +1038 -0
- package/packages/memory-engine/bench/scorecards-engine-via-shim/product-catalogue__pentatonic-memory__20260427-155503.json +937 -0
- package/packages/memory-engine/bench/scorecards-pentatonic-baseline/agent-coding__pentatonic-memory-latest__20260427-145103.json +1115 -0
- package/packages/memory-engine/bench/scorecards-pentatonic-baseline/agent-coding__pentatonic-memory__20260427-144909.json +1115 -0
- package/packages/memory-engine/bench/scorecards-pentatonic-baseline/chat-recall__pentatonic-memory-latest__20260427-145153.json +819 -0
- package/packages/memory-engine/bench/scorecards-pentatonic-baseline/chat-recall__pentatonic-memory__20260427-145120.json +542 -0
- package/packages/memory-engine/bench/scorecards-pentatonic-baseline/circular-economy__pentatonic-memory-latest__20260427-145313.json +1278 -0
- package/packages/memory-engine/bench/scorecards-pentatonic-baseline/circular-economy__pentatonic-memory__20260427-145207.json +894 -0
- package/packages/memory-engine/bench/scorecards-pentatonic-baseline/customer-support__pentatonic-memory-latest__20260427-145412.json +1018 -0
- package/packages/memory-engine/bench/scorecards-pentatonic-baseline/customer-support__pentatonic-memory__20260427-145327.json +680 -0
- package/packages/memory-engine/bench/scorecards-pentatonic-baseline/marketplace-ops__pentatonic-memory-latest__20260427-145517.json +1038 -0
- package/packages/memory-engine/bench/scorecards-pentatonic-baseline/marketplace-ops__pentatonic-memory__20260427-145422.json +693 -0
- package/packages/memory-engine/bench/scorecards-pentatonic-baseline/product-catalogue__pentatonic-memory-latest__20260427-145616.json +961 -0
- package/packages/memory-engine/bench/scorecards-pentatonic-baseline/product-catalogue__pentatonic-memory__20260427-145528.json +727 -0
- package/packages/memory-engine/compat/Dockerfile +11 -0
- package/packages/memory-engine/compat/server.py +680 -0
- package/packages/memory-engine/docker-compose.yml +243 -0
- package/packages/memory-engine/docs/MIGRATION.md +178 -0
- package/packages/memory-engine/docs/RUNBOOK-AWS.md +375 -0
- package/packages/memory-engine/docs/why-v05-underperforms.md +138 -0
- package/packages/memory-engine/engine/README.md +52 -0
- package/packages/memory-engine/engine/l2-hybridrag-proxy.py +1543 -0
- package/packages/memory-engine/engine/l5-comms-layer.py +663 -0
- package/packages/memory-engine/engine/l6-document-store.py +1018 -0
- package/packages/memory-engine/engine/services/l2/Dockerfile +41 -0
- package/packages/memory-engine/engine/services/l2/init_databases.py +81 -0
- package/packages/memory-engine/engine/services/l2/l2-hybridrag-proxy.py +1543 -0
- package/packages/memory-engine/engine/services/l4/Dockerfile +15 -0
- package/packages/memory-engine/engine/services/l4/server.py +235 -0
- package/packages/memory-engine/engine/services/l5/Dockerfile +9 -0
- package/packages/memory-engine/engine/services/l5/l5-comms-layer.py +678 -0
- package/packages/memory-engine/engine/services/l6/Dockerfile +11 -0
- package/packages/memory-engine/engine/services/l6/l6-document-store.py +1016 -0
- package/packages/memory-engine/engine/services/nv-embed/Dockerfile +28 -0
- package/packages/memory-engine/engine/services/nv-embed/server.py +152 -0
- package/packages/memory-engine/pme_memory/__init__.py +0 -0
- package/packages/memory-engine/pme_memory/__main__.py +129 -0
- package/packages/memory-engine/pme_memory/artifacts.py +95 -0
- package/packages/memory-engine/pme_memory/embed.py +74 -0
- package/packages/memory-engine/pme_memory/health.py +36 -0
- package/packages/memory-engine/pme_memory/hygiene.py +159 -0
- package/packages/memory-engine/pme_memory/indexer.py +200 -0
- package/packages/memory-engine/pme_memory/needs.py +55 -0
- package/packages/memory-engine/pme_memory/provenance.py +80 -0
- package/packages/memory-engine/pme_memory/scoring.py +168 -0
- package/packages/memory-engine/pme_memory/search.py +52 -0
- package/packages/memory-engine/pme_memory/store.py +86 -0
- package/packages/memory-engine/pme_memory/synthesis.py +114 -0
- package/packages/memory-engine/pyproject.toml +65 -0
- package/packages/memory-engine/scripts/kg-extractor.py +557 -0
- package/packages/memory-engine/scripts/kg-preflexor-v2.py +738 -0
- package/packages/memory-engine/tests/test_api_contract.sh +57 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus ingest adapters — concrete implementations of the
|
|
3
|
+
* { ingestChunk, deleteByCorpusFile }
|
|
4
|
+
* contract that the corpus pipeline depends on.
|
|
5
|
+
*
|
|
6
|
+
* `localAdapter` — writes via the existing memory.ingest() against a pg
|
|
7
|
+
* pool (or any query function). Stores corpus chunks
|
|
8
|
+
* in the `semantic` layer with metadata.source_file
|
|
9
|
+
* and metadata.corpus_file_key.
|
|
10
|
+
*
|
|
11
|
+
* `hostedAdapter` — calls the existing `createMemory` GraphQL mutation
|
|
12
|
+
* on deep-memory directly. This bypasses the
|
|
13
|
+
* STORE_MEMORY event queue (which has max_batch_size=1
|
|
14
|
+
* and would be the wrong shape for bulk ingest) and
|
|
15
|
+
* writes synchronously into the explicit `semantic`
|
|
16
|
+
* layer with full metadata.
|
|
17
|
+
*
|
|
18
|
+
* Companion TES PR required: the createMemory resolver
|
|
19
|
+
* currently hardcodes layerType: "episodic" even when
|
|
20
|
+
* layerId is supplied — see the audit notes in
|
|
21
|
+
* specs/01-onboarding-repo-ingest.md §12. Until that
|
|
22
|
+
* ships, chunks will land in episodic and may decay.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { ingest } from "../ingest.js";
|
|
26
|
+
import { buildHostedHeaders } from "../hosted.js";
|
|
27
|
+
|
|
28
|
+
const CREATE_MEMORY_MUTATION = `
|
|
29
|
+
mutation CreateMemory($clientId: String!, $layerId: String!, $content: String!, $metadata: JSON) {
|
|
30
|
+
createMemory(clientId: $clientId, layerId: $layerId, content: $content, metadata: $metadata) {
|
|
31
|
+
id
|
|
32
|
+
layer_id
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
const MEMORY_LAYERS_QUERY = `
|
|
38
|
+
query MemoryLayers($clientId: String!) {
|
|
39
|
+
memoryLayers(clientId: $clientId) {
|
|
40
|
+
id
|
|
41
|
+
name
|
|
42
|
+
layer_type
|
|
43
|
+
is_active
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
const DELETE_BY_METADATA_MUTATION = `
|
|
49
|
+
mutation DeleteMemoryNodesByMetadata($clientId: String!, $metadataKey: String!, $metadataValue: String!) {
|
|
50
|
+
deleteMemoryNodesByMetadata(clientId: $clientId, metadataKey: $metadataKey, metadataValue: $metadataValue)
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
const DEFAULT_LAYER = "semantic";
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build a local adapter against a memory system instance.
|
|
58
|
+
*
|
|
59
|
+
* @param {object} memory - createMemorySystem() result OR raw deps
|
|
60
|
+
* @param {object} opts
|
|
61
|
+
* @param {string} opts.clientId - Required, used as memory_layers.client_id
|
|
62
|
+
* @param {string} [opts.userId]
|
|
63
|
+
* @param {string} [opts.layer="semantic"]
|
|
64
|
+
* @returns {{ingestChunk, deleteByCorpusFile, init}}
|
|
65
|
+
*/
|
|
66
|
+
export function localAdapter(memory, opts = {}) {
|
|
67
|
+
if (!opts.clientId) throw new Error("localAdapter: clientId is required");
|
|
68
|
+
|
|
69
|
+
const layer = opts.layer || DEFAULT_LAYER;
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
/**
|
|
73
|
+
* One-time setup — ensure the target layer exists.
|
|
74
|
+
*/
|
|
75
|
+
async init() {
|
|
76
|
+
// memory.ensureLayers takes a single clientId and creates all four
|
|
77
|
+
// default layers idempotently. Safe to call repeatedly.
|
|
78
|
+
if (typeof memory.ensureLayers === "function") {
|
|
79
|
+
await memory.ensureLayers(opts.clientId);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async ingestChunk(content, metadata) {
|
|
84
|
+
// Code references aren't conversational user-stated facts, so the
|
|
85
|
+
// distill step (which runs an "extract atomic facts from a
|
|
86
|
+
// conversation" prompt) is at best wasted compute and at worst
|
|
87
|
+
// hallucinates "user" facts from code structure that pollute the
|
|
88
|
+
// semantic layer. Skip distillation for corpus ingest.
|
|
89
|
+
const ingestOpts = {
|
|
90
|
+
clientId: opts.clientId,
|
|
91
|
+
userId: opts.userId,
|
|
92
|
+
layerType: layer,
|
|
93
|
+
metadata,
|
|
94
|
+
distill: false,
|
|
95
|
+
};
|
|
96
|
+
// Use memory.ingest if we have the high-level API, otherwise fall
|
|
97
|
+
// back to the lower-level ingest() function with explicit deps.
|
|
98
|
+
if (typeof memory.ingest === "function") {
|
|
99
|
+
const result = await memory.ingest(content, ingestOpts);
|
|
100
|
+
return { id: result.id };
|
|
101
|
+
}
|
|
102
|
+
// Direct ingest() form — caller passed { db, ai, llm }
|
|
103
|
+
const result = await ingest(
|
|
104
|
+
memory.db,
|
|
105
|
+
memory.ai,
|
|
106
|
+
memory.llm,
|
|
107
|
+
content,
|
|
108
|
+
ingestOpts
|
|
109
|
+
);
|
|
110
|
+
return { id: result.id };
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Delete all memory_nodes whose metadata.corpus_file_key matches
|
|
115
|
+
* `${repoAbs}::${relPath}`. Used when re-ingesting a changed file
|
|
116
|
+
* or removing a vanished one.
|
|
117
|
+
*
|
|
118
|
+
* Returns the number of rows deleted.
|
|
119
|
+
*/
|
|
120
|
+
async deleteByCorpusFile(repoAbs, relPath) {
|
|
121
|
+
if (!memory.db) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
"localAdapter: deleteByCorpusFile requires { db } on the memory deps"
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const key = `${repoAbs}::${relPath}`;
|
|
127
|
+
const res = await memory.db(
|
|
128
|
+
`DELETE FROM memory_nodes
|
|
129
|
+
WHERE client_id = $1
|
|
130
|
+
AND metadata->>'corpus_file_key' = $2
|
|
131
|
+
RETURNING id`,
|
|
132
|
+
[opts.clientId, key]
|
|
133
|
+
);
|
|
134
|
+
return (res.rows || []).length;
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Hosted adapter — calls the deep-memory `createMemory` GraphQL
|
|
141
|
+
* mutation directly so corpus chunks land in the chosen layer
|
|
142
|
+
* (semantic by default) with embedding + HyDE applied server-side.
|
|
143
|
+
*
|
|
144
|
+
* Why not events?
|
|
145
|
+
* The STORE_MEMORY event path is fine for chat-turn fan-out (one
|
|
146
|
+
* event per conversation turn) but is the wrong shape for bulk
|
|
147
|
+
* corpus ingest. The Cloudflare consumer queue is configured with
|
|
148
|
+
* max_batch_size=1 and max_concurrency=30 (see
|
|
149
|
+
* thing-event-system/workers/wrangler.epic.toml), so 12,000 chunks
|
|
150
|
+
* would mean 12,000 individual consumer invocations — slow, and
|
|
151
|
+
* the existing consumer also hardcodes layer routing to "episodic"
|
|
152
|
+
* (which would decay code chunks within days). Calling the GraphQL
|
|
153
|
+
* mutation directly is faster, cheaper, and lets us specify the
|
|
154
|
+
* layer explicitly.
|
|
155
|
+
*
|
|
156
|
+
* Required TES companion fix:
|
|
157
|
+
* modules/deep-memory/graphql/memory/resolvers.js#createMemory
|
|
158
|
+
* accepts a layerId argument and validates the row exists, but
|
|
159
|
+
* then ignores it and writes to "episodic" anyway. A one-line
|
|
160
|
+
* change to read layer_type from the validated row makes it honor
|
|
161
|
+
* the request. Until that lands, chunks ingested via this adapter
|
|
162
|
+
* will land in episodic; once it lands, they go to whatever layer
|
|
163
|
+
* was requested (default: semantic).
|
|
164
|
+
*
|
|
165
|
+
* Deletion:
|
|
166
|
+
* No `deleteMemoriesByMetadata` mutation exists today. The adapter
|
|
167
|
+
* logs the intent locally but cannot drop server-side chunks for
|
|
168
|
+
* removed files — those will accumulate. Documented as a follow-up.
|
|
169
|
+
*
|
|
170
|
+
* @param {object} config - { endpoint, clientId, apiKey } (or tes_* legacy)
|
|
171
|
+
* @param {object} [opts]
|
|
172
|
+
* @param {number} [opts.timeoutMs=15000]
|
|
173
|
+
* @param {string} [opts.layerName="semantic"] - Target layer name; resolved to layerId via memoryLayers query on first call
|
|
174
|
+
*/
|
|
175
|
+
export function hostedAdapter(config, opts = {}) {
|
|
176
|
+
const timeoutMs = opts.timeoutMs ?? 15000;
|
|
177
|
+
const layerName = opts.layerName || DEFAULT_LAYER;
|
|
178
|
+
|
|
179
|
+
const endpoint = config.endpoint || config.tes_endpoint;
|
|
180
|
+
const clientId = config.clientId || config.tes_client_id;
|
|
181
|
+
const apiKey = config.apiKey || config.tes_api_key;
|
|
182
|
+
if (!endpoint || !clientId || !apiKey) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
"hostedAdapter: requires { endpoint, clientId, apiKey } (tes_* keys also accepted)"
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
const cfg = { endpoint, clientId, apiKey };
|
|
188
|
+
const headers = buildHostedHeaders(cfg);
|
|
189
|
+
|
|
190
|
+
let layerId = null;
|
|
191
|
+
let layerLookupPromise = null;
|
|
192
|
+
|
|
193
|
+
async function graphql(query, variables) {
|
|
194
|
+
const controller = new AbortController();
|
|
195
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
196
|
+
try {
|
|
197
|
+
const res = await fetch(`${cfg.endpoint}/api/graphql`, {
|
|
198
|
+
method: "POST",
|
|
199
|
+
headers,
|
|
200
|
+
body: JSON.stringify({ query, variables }),
|
|
201
|
+
signal: controller.signal,
|
|
202
|
+
});
|
|
203
|
+
clearTimeout(timer);
|
|
204
|
+
if (!res.ok) {
|
|
205
|
+
return { error: `tes_http_${res.status}` };
|
|
206
|
+
}
|
|
207
|
+
const body = await res.json();
|
|
208
|
+
if (body?.errors?.length) {
|
|
209
|
+
return {
|
|
210
|
+
error: `tes_graphql_error:${body.errors[0]?.message}`,
|
|
211
|
+
errors: body.errors,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return { data: body.data };
|
|
215
|
+
} catch (err) {
|
|
216
|
+
clearTimeout(timer);
|
|
217
|
+
return {
|
|
218
|
+
error: err.name === "AbortError" ? "tes_timeout" : "tes_unreachable",
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function resolveLayerId() {
|
|
224
|
+
if (layerId) return layerId;
|
|
225
|
+
if (layerLookupPromise) return layerLookupPromise;
|
|
226
|
+
|
|
227
|
+
layerLookupPromise = (async () => {
|
|
228
|
+
const result = await graphql(MEMORY_LAYERS_QUERY, { clientId });
|
|
229
|
+
if (result.error) {
|
|
230
|
+
throw new Error(`hostedAdapter: layer lookup failed (${result.error})`);
|
|
231
|
+
}
|
|
232
|
+
const layers = result.data?.memoryLayers || [];
|
|
233
|
+
const match = layers.find(
|
|
234
|
+
(l) => l.is_active && (l.name === layerName || l.layer_type === layerName)
|
|
235
|
+
);
|
|
236
|
+
if (!match) {
|
|
237
|
+
throw new Error(
|
|
238
|
+
`hostedAdapter: no active layer named "${layerName}" for client ${clientId}. ` +
|
|
239
|
+
`Available: ${layers.map((l) => l.name).join(", ") || "<none>"}. ` +
|
|
240
|
+
`Ensure the deep-memory module is enabled in the TES dashboard.`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
layerId = match.id;
|
|
244
|
+
return layerId;
|
|
245
|
+
})();
|
|
246
|
+
return layerLookupPromise;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
async init() {
|
|
251
|
+
// Resolve the target layerId now so we fail fast if the module
|
|
252
|
+
// isn't enabled or the layer doesn't exist.
|
|
253
|
+
await resolveLayerId();
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
async ingestChunk(content, metadata) {
|
|
257
|
+
// Note: distillation happens (or not) server-side. We can't pass
|
|
258
|
+
// distill:false through createMemory the way localAdapter does.
|
|
259
|
+
// metadata.kind = "code_reference" is the signal downstream
|
|
260
|
+
// consumers should branch on to skip the conversation-shaped
|
|
261
|
+
// distiller. Tracked as a follow-up TES change.
|
|
262
|
+
const lid = await resolveLayerId();
|
|
263
|
+
const result = await graphql(CREATE_MEMORY_MUTATION, {
|
|
264
|
+
clientId,
|
|
265
|
+
layerId: lid,
|
|
266
|
+
content,
|
|
267
|
+
metadata,
|
|
268
|
+
});
|
|
269
|
+
if (result.error) return { skipped: result.error };
|
|
270
|
+
return { id: result.data?.createMemory?.id };
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
async deleteByCorpusFile(repoAbs, relPath) {
|
|
274
|
+
// Calls the deleteMemoryNodesByMetadata mutation added in TES
|
|
275
|
+
// PR #245. Server-side returns the rowcount; we propagate it.
|
|
276
|
+
// If the mutation isn't deployed yet (older TES tenant), the
|
|
277
|
+
// GraphQL error is swallowed as a skipped delete — the SDK's
|
|
278
|
+
// local state still drops the entry on its side, and orphaned
|
|
279
|
+
// server-side chunks accumulate until the TES PR lands.
|
|
280
|
+
const key = `${repoAbs}::${relPath}`;
|
|
281
|
+
const result = await graphql(DELETE_BY_METADATA_MUTATION, {
|
|
282
|
+
clientId,
|
|
283
|
+
metadataKey: "corpus_file_key",
|
|
284
|
+
metadataValue: key,
|
|
285
|
+
});
|
|
286
|
+
if (result.error) {
|
|
287
|
+
// Older TES tenants (pre-PR-245) will reject the unknown
|
|
288
|
+
// mutation; treat as zero deletions rather than throwing.
|
|
289
|
+
return 0;
|
|
290
|
+
}
|
|
291
|
+
return result.data?.deleteMemoryNodesByMetadata || 0;
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Engine adapter — talks directly to the memory engine's HTTP API
|
|
298
|
+
* (`/store`, `/store-batch`, `/forget`) at `engineUrl`. Used for the
|
|
299
|
+
* local-OSS path where no TES is involved, and for any case where the
|
|
300
|
+
* caller wants to ingest straight into a Pentatonic-managed engine
|
|
301
|
+
* without going through the TES GraphQL surface.
|
|
302
|
+
*
|
|
303
|
+
* Wire format matches the engine's compat shim. References-mode
|
|
304
|
+
* metadata (kind: "code_reference") and arbitrary metadata pass
|
|
305
|
+
* through as JSONB on the engine side.
|
|
306
|
+
*
|
|
307
|
+
* @param {object} config
|
|
308
|
+
* @param {string} config.engineUrl - e.g. "http://localhost:8099"
|
|
309
|
+
* @param {string} [config.arena] - tenant scope; defaults to "default"
|
|
310
|
+
* @param {string} [config.apiKey] - optional Authorization: Bearer
|
|
311
|
+
* @param {object} [opts]
|
|
312
|
+
* @param {number} [opts.timeoutMs=30000]
|
|
313
|
+
* @returns {{ingestChunk, deleteByCorpusFile, init}}
|
|
314
|
+
*/
|
|
315
|
+
export function engineAdapter(config, opts = {}) {
|
|
316
|
+
const engineUrl = (config.engineUrl || "").replace(/\/$/, "");
|
|
317
|
+
if (!engineUrl) {
|
|
318
|
+
throw new Error("engineAdapter: engineUrl is required");
|
|
319
|
+
}
|
|
320
|
+
const arena = config.arena || "default";
|
|
321
|
+
const apiKey = config.apiKey || null;
|
|
322
|
+
const timeoutMs = opts.timeoutMs ?? 30000;
|
|
323
|
+
|
|
324
|
+
function headers() {
|
|
325
|
+
const h = { "content-type": "application/json" };
|
|
326
|
+
if (apiKey) h["authorization"] = `Bearer ${apiKey}`;
|
|
327
|
+
return h;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function http(path, body) {
|
|
331
|
+
const controller = new AbortController();
|
|
332
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
333
|
+
try {
|
|
334
|
+
const res = await fetch(`${engineUrl}${path}`, {
|
|
335
|
+
method: "POST",
|
|
336
|
+
headers: headers(),
|
|
337
|
+
body: JSON.stringify(body),
|
|
338
|
+
signal: controller.signal,
|
|
339
|
+
});
|
|
340
|
+
clearTimeout(timer);
|
|
341
|
+
if (!res.ok) return { error: `engine_http_${res.status}` };
|
|
342
|
+
return { data: await res.json() };
|
|
343
|
+
} catch (err) {
|
|
344
|
+
clearTimeout(timer);
|
|
345
|
+
return {
|
|
346
|
+
error: err.name === "AbortError" ? "engine_timeout" : "engine_unreachable",
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
/**
|
|
353
|
+
* Verify the engine is reachable before kicking off ingest.
|
|
354
|
+
* Engine /health returns 200 even when individual layers are
|
|
355
|
+
* "degraded"; we just check the HTTP path works.
|
|
356
|
+
*/
|
|
357
|
+
async init() {
|
|
358
|
+
const controller = new AbortController();
|
|
359
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
360
|
+
try {
|
|
361
|
+
const res = await fetch(`${engineUrl}/health`, {
|
|
362
|
+
headers: headers(),
|
|
363
|
+
signal: controller.signal,
|
|
364
|
+
});
|
|
365
|
+
clearTimeout(timer);
|
|
366
|
+
if (!res.ok) {
|
|
367
|
+
throw new Error(`engineAdapter: /health returned ${res.status}`);
|
|
368
|
+
}
|
|
369
|
+
} catch (err) {
|
|
370
|
+
clearTimeout(timer);
|
|
371
|
+
throw new Error(
|
|
372
|
+
`engineAdapter: engine at ${engineUrl} unreachable (${err.message})`
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
|
|
377
|
+
async ingestChunk(content, metadata) {
|
|
378
|
+
// Engine ingests via /store; one chunk per call. The corpus
|
|
379
|
+
// pipeline batches at the file level, but each chunk is its own
|
|
380
|
+
// /store call so we get a per-chunk id back. /store-batch is
|
|
381
|
+
// available for future bulk ingest if/when the pipeline rewires.
|
|
382
|
+
const body = { content, metadata: { ...metadata, arena } };
|
|
383
|
+
const result = await http("/store", body);
|
|
384
|
+
if (result.error) return { skipped: result.error };
|
|
385
|
+
return { id: result.data?.id };
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
async deleteByCorpusFile(repoAbs, relPath) {
|
|
389
|
+
const key = `${repoAbs}::${relPath}`;
|
|
390
|
+
const result = await http("/forget", {
|
|
391
|
+
metadata_contains: { corpus_file_key: key },
|
|
392
|
+
arena,
|
|
393
|
+
});
|
|
394
|
+
if (result.error) return 0;
|
|
395
|
+
return result.data?.deleted ?? 0;
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
}
|