@pentatonic-ai/ai-agent-sdk 0.7.11 → 0.7.12
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pentatonic-ai/ai-agent-sdk",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.12",
|
|
4
4
|
"description": "TES SDK — LLM observability and lifecycle tracking via Pentatonic Thing Event System. Track token usage, tool calls, and conversations. Manage things through event-sourced lifecycle stages with AI enrichment and vector search.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -231,6 +231,63 @@ describe("engine HTTP client", () => {
|
|
|
231
231
|
});
|
|
232
232
|
});
|
|
233
233
|
|
|
234
|
+
describe("opts.headers passthrough (CF Access)", () => {
|
|
235
|
+
const cfAccess = {
|
|
236
|
+
"CF-Access-Client-Id": "tes-worker.id",
|
|
237
|
+
"CF-Access-Client-Secret": "shh-it-secret",
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
it("fetchEngine merges opts.headers on top of the default content-type", async () => {
|
|
241
|
+
mockOk({});
|
|
242
|
+
await fetchEngine("https://e", "/store", { a: 1 }, { headers: cfAccess });
|
|
243
|
+
const sent = calls[0].init.headers;
|
|
244
|
+
expect(sent["content-type"]).toBe("application/json");
|
|
245
|
+
expect(sent["CF-Access-Client-Id"]).toBe("tes-worker.id");
|
|
246
|
+
expect(sent["CF-Access-Client-Secret"]).toBe("shh-it-secret");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("engineStore forwards opts.headers", async () => {
|
|
250
|
+
mockOk({ id: "x", content: "x", layerId: "ml_acme_episodic" });
|
|
251
|
+
await engineStore("https://e", {
|
|
252
|
+
clientId: "acme",
|
|
253
|
+
content: "x",
|
|
254
|
+
headers: cfAccess,
|
|
255
|
+
});
|
|
256
|
+
const sent = calls[0].init.headers;
|
|
257
|
+
expect(sent["CF-Access-Client-Id"]).toBe("tes-worker.id");
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("engineSearch forwards opts.headers", async () => {
|
|
261
|
+
mockOk({ results: [] });
|
|
262
|
+
await engineSearch("https://e", {
|
|
263
|
+
clientId: "acme",
|
|
264
|
+
query: "x",
|
|
265
|
+
headers: cfAccess,
|
|
266
|
+
});
|
|
267
|
+
const sent = calls[0].init.headers;
|
|
268
|
+
expect(sent["CF-Access-Client-Id"]).toBe("tes-worker.id");
|
|
269
|
+
expect(sent["CF-Access-Client-Secret"]).toBe("shh-it-secret");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("engineForget forwards opts.headers", async () => {
|
|
273
|
+
mockOk({ deleted: 0 });
|
|
274
|
+
await engineForget("https://e", {
|
|
275
|
+
clientId: "acme",
|
|
276
|
+
id: "abc",
|
|
277
|
+
headers: cfAccess,
|
|
278
|
+
});
|
|
279
|
+
const sent = calls[0].init.headers;
|
|
280
|
+
expect(sent["CF-Access-Client-Id"]).toBe("tes-worker.id");
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("no headers sent when opts.headers omitted (back-compat)", async () => {
|
|
284
|
+
mockOk({});
|
|
285
|
+
await engineStore("https://e", { clientId: "acme", content: "x" });
|
|
286
|
+
const sent = calls[0].init.headers;
|
|
287
|
+
expect(Object.keys(sent)).toEqual(["content-type"]);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
234
291
|
describe("composeArena", () => {
|
|
235
292
|
it("tenant scope by default when no userId", () => {
|
|
236
293
|
expect(composeArena("acme")).toBe("acme");
|
|
@@ -308,6 +308,11 @@ export function hostedAdapter(config, opts = {}) {
|
|
|
308
308
|
* @param {string} config.engineUrl - e.g. "http://localhost:8099"
|
|
309
309
|
* @param {string} [config.arena] - tenant scope; defaults to "default"
|
|
310
310
|
* @param {string} [config.apiKey] - optional Authorization: Bearer
|
|
311
|
+
* @param {string} [config.cfAccessClientId] - CF Access service token id;
|
|
312
|
+
* sent as `CF-Access-Client-Id` when the engine is locked behind a
|
|
313
|
+
* Cloudflare Access policy.
|
|
314
|
+
* @param {string} [config.cfAccessClientSecret] - paired secret; sent as
|
|
315
|
+
* `CF-Access-Client-Secret`.
|
|
311
316
|
* @param {object} [opts]
|
|
312
317
|
* @param {number} [opts.timeoutMs=30000]
|
|
313
318
|
* @returns {{ingestChunk, deleteByCorpusFile, init}}
|
|
@@ -319,11 +324,15 @@ export function engineAdapter(config, opts = {}) {
|
|
|
319
324
|
}
|
|
320
325
|
const arena = config.arena || "default";
|
|
321
326
|
const apiKey = config.apiKey || null;
|
|
327
|
+
const cfAccessId = config.cfAccessClientId || null;
|
|
328
|
+
const cfAccessSecret = config.cfAccessClientSecret || null;
|
|
322
329
|
const timeoutMs = opts.timeoutMs ?? 30000;
|
|
323
330
|
|
|
324
331
|
function headers() {
|
|
325
332
|
const h = { "content-type": "application/json" };
|
|
326
333
|
if (apiKey) h["authorization"] = `Bearer ${apiKey}`;
|
|
334
|
+
if (cfAccessId) h["CF-Access-Client-Id"] = cfAccessId;
|
|
335
|
+
if (cfAccessSecret) h["CF-Access-Client-Secret"] = cfAccessSecret;
|
|
327
336
|
return h;
|
|
328
337
|
}
|
|
329
338
|
|
|
@@ -99,6 +99,13 @@ function readPluginConfig() {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
function buildAdapterOrFail() {
|
|
102
|
+
// CF Access service token (optional). When the engine domain is
|
|
103
|
+
// locked behind a CF Access policy, callers need these headers or
|
|
104
|
+
// the edge returns 403 before traffic reaches the tunnel. Env-var
|
|
105
|
+
// names match the canonical Cloudflare Access convention.
|
|
106
|
+
const cfAccessClientId = process.env.CF_ACCESS_CLIENT_ID || null;
|
|
107
|
+
const cfAccessClientSecret = process.env.CF_ACCESS_CLIENT_SECRET || null;
|
|
108
|
+
|
|
102
109
|
// 1. Env-var override (CI / scripts / explicit). Highest precedence.
|
|
103
110
|
const envEngineUrl =
|
|
104
111
|
process.env.MEMORY_ENGINE_URL || process.env.PENTATONIC_ENGINE_URL || null;
|
|
@@ -114,6 +121,8 @@ function buildAdapterOrFail() {
|
|
|
114
121
|
engineUrl: envEngineUrl,
|
|
115
122
|
arena,
|
|
116
123
|
apiKey: process.env.MEMORY_ENGINE_API_KEY || null,
|
|
124
|
+
cfAccessClientId,
|
|
125
|
+
cfAccessClientSecret,
|
|
117
126
|
}),
|
|
118
127
|
};
|
|
119
128
|
}
|
|
@@ -124,7 +133,12 @@ function buildAdapterOrFail() {
|
|
|
124
133
|
const arena = pluginConfig.client_id || "default";
|
|
125
134
|
return {
|
|
126
135
|
tenant: { source: `plugin-config (${pluginConfig._path})`, engineUrl: pluginConfig.memory_url, arena },
|
|
127
|
-
adapter: engineAdapter({
|
|
136
|
+
adapter: engineAdapter({
|
|
137
|
+
engineUrl: pluginConfig.memory_url,
|
|
138
|
+
arena,
|
|
139
|
+
cfAccessClientId,
|
|
140
|
+
cfAccessClientSecret,
|
|
141
|
+
}),
|
|
128
142
|
};
|
|
129
143
|
}
|
|
130
144
|
|
|
@@ -67,19 +67,31 @@ export const DEFAULT_MIN_SCORE = 0.3;
|
|
|
67
67
|
* @param {string} engineUrl - engine base URL (no trailing slash)
|
|
68
68
|
* @param {string} path - "/store" | "/search" | "/forget" | "/health" | "/store-batch"
|
|
69
69
|
* @param {object} body - JSON body, serialised verbatim
|
|
70
|
+
* @param {object} [opts]
|
|
71
|
+
* @param {Record<string,string>} [opts.headers] - additional request
|
|
72
|
+
* headers; merged on top of the default `content-type`. Canonical
|
|
73
|
+
* use case is Cloudflare Access service tokens
|
|
74
|
+
* (`CF-Access-Client-Id` + `CF-Access-Client-Secret`) when the
|
|
75
|
+
* engine domain is locked behind a CF Access policy. Any auth
|
|
76
|
+
* scheme can flow through this — the helper makes no assumptions
|
|
77
|
+
* about header names.
|
|
70
78
|
* @returns {Promise<object>} parsed JSON response
|
|
71
79
|
* @throws {Error} - "engine_<status>" with `.detail` set to response text
|
|
72
80
|
* on HTTP non-2xx, or "engine_network: <msg>" on
|
|
73
81
|
* transport failure.
|
|
74
82
|
*/
|
|
75
|
-
export async function fetchEngine(engineUrl, path, body) {
|
|
83
|
+
export async function fetchEngine(engineUrl, path, body, opts = {}) {
|
|
76
84
|
const base = engineUrl || DEFAULT_ENGINE_URL;
|
|
77
85
|
const url = `${base}${path}`;
|
|
86
|
+
// Default content-type first so callers can override it via opts.headers
|
|
87
|
+
// if they ever need to (none do today, but the spread makes the rule
|
|
88
|
+
// explicit: caller wins on conflict).
|
|
89
|
+
const headers = { "content-type": "application/json", ...(opts.headers || {}) };
|
|
78
90
|
let res;
|
|
79
91
|
try {
|
|
80
92
|
res = await fetch(url, {
|
|
81
93
|
method: "POST",
|
|
82
|
-
headers
|
|
94
|
+
headers,
|
|
83
95
|
body: JSON.stringify(body),
|
|
84
96
|
});
|
|
85
97
|
} catch (err) {
|
|
@@ -163,6 +175,9 @@ export function composeArenas(clientId, userId) {
|
|
|
163
175
|
* @param {object} [opts.metadata] extra metadata; merged into engine body
|
|
164
176
|
* @param {string} [opts.layerType] "episodic" | "semantic" | "procedural" | "working"
|
|
165
177
|
* @param {string} [opts.actorUserId] passes through as metadata.actor_user_id
|
|
178
|
+
* @param {Record<string,string>} [opts.headers] forwarded HTTP headers
|
|
179
|
+
* (e.g. CF Access service token pair when the engine domain is
|
|
180
|
+
* locked behind a Cloudflare Access policy).
|
|
166
181
|
* @returns {Promise<EngineStoreResult>}
|
|
167
182
|
*/
|
|
168
183
|
export async function engineStore(engineUrl, opts) {
|
|
@@ -174,6 +189,7 @@ export async function engineStore(engineUrl, opts) {
|
|
|
174
189
|
metadata = {},
|
|
175
190
|
layerType,
|
|
176
191
|
actorUserId,
|
|
192
|
+
headers,
|
|
177
193
|
} = opts || {};
|
|
178
194
|
if (!clientId) throw new Error("engineStore: clientId required");
|
|
179
195
|
if (typeof content !== "string") throw new Error("engineStore: content required");
|
|
@@ -187,7 +203,7 @@ export async function engineStore(engineUrl, opts) {
|
|
|
187
203
|
...(actorUserId !== undefined ? { actor_user_id: actorUserId } : {}),
|
|
188
204
|
},
|
|
189
205
|
};
|
|
190
|
-
return fetchEngine(engineUrl, "/store", body);
|
|
206
|
+
return fetchEngine(engineUrl, "/store", body, { headers });
|
|
191
207
|
}
|
|
192
208
|
|
|
193
209
|
/**
|
|
@@ -206,6 +222,8 @@ export async function engineStore(engineUrl, opts) {
|
|
|
206
222
|
* @param {number} [opts.limit=10]
|
|
207
223
|
* @param {number} [opts.minScore=0.3]
|
|
208
224
|
* @param {object} [opts.metadataFilter] arbitrary equality filter on result metadata
|
|
225
|
+
* @param {Record<string,string>} [opts.headers] forwarded HTTP headers
|
|
226
|
+
* (e.g. CF Access service token pair).
|
|
209
227
|
* @returns {Promise<{results: EngineSearchHit[]}>}
|
|
210
228
|
*/
|
|
211
229
|
export async function engineSearch(engineUrl, opts) {
|
|
@@ -216,6 +234,7 @@ export async function engineSearch(engineUrl, opts) {
|
|
|
216
234
|
limit = DEFAULT_LIMIT,
|
|
217
235
|
minScore = DEFAULT_MIN_SCORE,
|
|
218
236
|
metadataFilter,
|
|
237
|
+
headers,
|
|
219
238
|
} = opts || {};
|
|
220
239
|
if (!clientId) throw new Error("engineSearch: clientId required");
|
|
221
240
|
if (typeof query !== "string") throw new Error("engineSearch: query required");
|
|
@@ -232,7 +251,7 @@ export async function engineSearch(engineUrl, opts) {
|
|
|
232
251
|
? { metadata_filter: metadataFilter }
|
|
233
252
|
: {}),
|
|
234
253
|
};
|
|
235
|
-
return fetchEngine(engineUrl, "/search", body);
|
|
254
|
+
return fetchEngine(engineUrl, "/search", body, { headers });
|
|
236
255
|
}
|
|
237
256
|
|
|
238
257
|
/**
|
|
@@ -245,10 +264,12 @@ export async function engineSearch(engineUrl, opts) {
|
|
|
245
264
|
* @param {string} opts.clientId
|
|
246
265
|
* @param {string} [opts.id] forget a single record by engine id
|
|
247
266
|
* @param {object} [opts.metadataContains] forget all records matching every key=value pair
|
|
267
|
+
* @param {Record<string,string>} [opts.headers] forwarded HTTP headers
|
|
268
|
+
* (e.g. CF Access service token pair).
|
|
248
269
|
* @returns {Promise<{deleted: number}>}
|
|
249
270
|
*/
|
|
250
271
|
export async function engineForget(engineUrl, opts) {
|
|
251
|
-
const { clientId, id, metadataContains } = opts || {};
|
|
272
|
+
const { clientId, id, metadataContains, headers } = opts || {};
|
|
252
273
|
if (!clientId) throw new Error("engineForget: clientId required");
|
|
253
274
|
if (!id && !metadataContains) {
|
|
254
275
|
throw new Error("engineForget: provide id or metadataContains");
|
|
@@ -258,5 +279,5 @@ export async function engineForget(engineUrl, opts) {
|
|
|
258
279
|
...(id ? { id } : {}),
|
|
259
280
|
...(metadataContains ? { metadata_contains: metadataContains } : {}),
|
|
260
281
|
};
|
|
261
|
-
return fetchEngine(engineUrl, "/forget", body);
|
|
282
|
+
return fetchEngine(engineUrl, "/forget", body, { headers });
|
|
262
283
|
}
|