hebbrix 2.1.0 → 2.2.1

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/dist/index.mjs CHANGED
@@ -1,3 +1,57 @@
1
+ // src/safety.ts
2
+ var REQUIRED_FIELDS = [
3
+ "no_match",
4
+ "abstain_recommended",
5
+ "query_confidence",
6
+ "grounding",
7
+ "evidence_ids",
8
+ "safety_contract_version"
9
+ ];
10
+ function enforceSearchSafety(response, rowsKey = "results") {
11
+ const data = { ...response };
12
+ const rawRows = data[rowsKey];
13
+ const rows = Array.isArray(rawRows) ? rawRows.filter((row) => typeof row === "object" && row !== null) : [];
14
+ const missing = REQUIRED_FIELDS.filter(
15
+ (field) => !Object.prototype.hasOwnProperty.call(data, field)
16
+ );
17
+ let reason;
18
+ if (missing.length > 0) {
19
+ reason = `missing_safety_fields:${missing.join(",")}`;
20
+ } else if (typeof data.no_match !== "boolean" || typeof data.abstain_recommended !== "boolean") {
21
+ reason = "invalid_abstention_fields";
22
+ } else if (typeof data.query_confidence !== "number" || !Number.isFinite(data.query_confidence) || data.query_confidence < 0 || data.query_confidence > 1) {
23
+ reason = "invalid_query_confidence";
24
+ } else if (typeof data.grounding !== "object" || data.grounding === null || Array.isArray(data.grounding)) {
25
+ reason = "invalid_grounding_receipt";
26
+ } else if (!Array.isArray(data.evidence_ids)) {
27
+ reason = "invalid_evidence_ids";
28
+ } else {
29
+ const evidenceIds = new Set(data.evidence_ids.map(String));
30
+ const rowIds = rows.map((row) => row.memory_id ?? row.id).filter((value) => typeof value === "string" && value.length > 0);
31
+ if (rowIds.some((value) => !evidenceIds.has(value))) {
32
+ reason = "rows_not_bound_to_evidence_ids";
33
+ } else if (data.no_match && (rowIds.length > 0 || evidenceIds.size > 0)) {
34
+ reason = "no_match_contains_evidence";
35
+ }
36
+ }
37
+ if (reason || data.no_match === true) {
38
+ data[rowsKey] = [];
39
+ if (rowsKey === "results") data.total = 0;
40
+ data.no_match = true;
41
+ data.abstain_recommended = true;
42
+ data.query_confidence = 0;
43
+ data.evidence_ids = [];
44
+ data.evidence_claims = [];
45
+ if (reason) {
46
+ data.sdk_safety_reason = reason;
47
+ data.grounding = { status: "no_grounded_match", reason };
48
+ }
49
+ } else if (data.degraded === true || data.abstain_recommended === true) {
50
+ data.sdk_safety_reason = "degraded_evidence_preserved";
51
+ }
52
+ return data;
53
+ }
54
+
1
55
  // src/resources.ts
2
56
  var BaseResource = class {
3
57
  constructor(client) {
@@ -77,14 +131,104 @@ var MemoriesResource = class extends BaseResource {
77
131
  * Create a new memory
78
132
  */
79
133
  async create(params) {
80
- return this.client.post("/v1/memories", params);
134
+ if (!params.content?.trim() && !params.messages?.length) {
135
+ throw new TypeError("content or messages must be provided");
136
+ }
137
+ const { idempotency_key, ...input } = params;
138
+ return this.client.request("POST", "/v1/memories", {
139
+ headers: idempotency_key ? { "Idempotency-Key": idempotency_key } : void 0,
140
+ body: JSON.stringify({
141
+ source_type: "text",
142
+ metadata: {},
143
+ infer: false,
144
+ wait_for_index: false,
145
+ ...input
146
+ })
147
+ });
148
+ }
149
+ /**
150
+ * Create up to 100 memories with one unambiguous readiness contract.
151
+ * `wait_for_index=true` resolves only for a fully searchable batch; a server
152
+ * deadline or indexing failure rejects the request instead of returning a
153
+ * successful processing receipt.
154
+ */
155
+ async createBatch(params) {
156
+ if (!params.memories?.length || params.memories.length > 100) {
157
+ throw new TypeError("memories must contain between 1 and 100 items");
158
+ }
159
+ if (params.memories.some((item) => !item.content?.trim())) {
160
+ throw new TypeError("every batch memory must contain non-empty content");
161
+ }
162
+ const { idempotency_key, signal, ...body } = params;
163
+ const receipt = await this.client.request(
164
+ "POST",
165
+ "/v1/memories/batch",
166
+ {
167
+ headers: idempotency_key ? { "Idempotency-Key": idempotency_key } : void 0,
168
+ body: JSON.stringify({ wait_for_index: false, ...body }),
169
+ signal
170
+ }
171
+ );
172
+ if (params.wait_for_index && !(receipt.searchable === true && receipt.processing_status === "completed")) {
173
+ throw new Error(
174
+ "wait_for_index batch response was not fully searchable; retry with the same Idempotency-Key"
175
+ );
176
+ }
177
+ return receipt;
178
+ }
179
+ /** Poll every item in an asynchronous batch receipt until it is searchable. */
180
+ async waitForBatchSearchable(receipt, options = {}) {
181
+ const ids = [...new Set(receipt.memory_ids || [])];
182
+ if (!ids.length) {
183
+ throw new Error("batch receipt does not contain memory_ids");
184
+ }
185
+ const timeoutMs = Math.max(0, options.timeoutMs ?? 6e4);
186
+ const pollIntervalMs = Math.max(50, options.pollIntervalMs ?? 500);
187
+ const deadline = Date.now() + timeoutMs;
188
+ while (true) {
189
+ if (options.signal?.aborted) {
190
+ throw options.signal.reason || new Error("batch readiness polling was aborted");
191
+ }
192
+ const rows = await Promise.all(ids.map((id) => this.get(id)));
193
+ const terminal = rows.find(
194
+ (row) => ["failed", "cancelled", "canceled"].includes(
195
+ String(row.processing_status || "").toLowerCase()
196
+ )
197
+ );
198
+ if (terminal) {
199
+ throw new Error(
200
+ `memory ${terminal.id} indexing reached terminal state ${terminal.processing_status}`
201
+ );
202
+ }
203
+ if (rows.every((row) => row.searchable === true)) {
204
+ return {
205
+ ...receipt,
206
+ processing_status: "completed",
207
+ searchable: true,
208
+ results: ids.map((id) => ({
209
+ id,
210
+ memory_id: id,
211
+ processing_status: "completed"
212
+ }))
213
+ };
214
+ }
215
+ if (Date.now() >= deadline) {
216
+ throw new Error(`batch was not searchable within ${timeoutMs}ms`);
217
+ }
218
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
219
+ }
81
220
  }
82
221
  /**
83
222
  * List memories
84
223
  */
85
- async list(params = {}) {
224
+ async listPage(params = {}) {
86
225
  return this.client.get("/v1/memories", params);
87
226
  }
227
+ /** Back-compatible one-page convenience; use listPage for cursor metadata. */
228
+ async list(params = {}) {
229
+ const page = await this.listPage(params);
230
+ return page.items;
231
+ }
88
232
  /**
89
233
  * Get a specific memory
90
234
  */
@@ -104,30 +248,79 @@ var MemoriesResource = class extends BaseResource {
104
248
  await this.client.delete(`/v1/memories/${memoryId}`);
105
249
  }
106
250
  };
251
+ var MemoryJobsResource = class extends BaseResource {
252
+ async get(jobId) {
253
+ return this.client.get(`/v1/memory-jobs/${jobId}`);
254
+ }
255
+ async wait(jobId, options = {}) {
256
+ const timeoutMs = Math.max(0, options.timeoutMs ?? 6e4);
257
+ const pollIntervalMs = Math.max(50, options.pollIntervalMs ?? 500);
258
+ const deadline = Date.now() + timeoutMs;
259
+ while (true) {
260
+ const receipt = await this.get(jobId);
261
+ const status = String(receipt.status || "").toLowerCase();
262
+ if (["completed", "failed", "cancelled", "canceled"].includes(status)) {
263
+ return receipt;
264
+ }
265
+ if (Date.now() >= deadline) {
266
+ throw new Error(`memory job ${jobId} did not finish in ${timeoutMs}ms`);
267
+ }
268
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
269
+ }
270
+ }
271
+ };
272
+ var CorrectionsResource = class extends BaseResource {
273
+ async create(params) {
274
+ const { idempotency_key, ...body } = params;
275
+ return this.client.request("POST", "/v1/corrections", {
276
+ headers: idempotency_key ? { "Idempotency-Key": idempotency_key } : void 0,
277
+ body: JSON.stringify({
278
+ correction_type: "preference",
279
+ confidence: 1,
280
+ ...body
281
+ })
282
+ });
283
+ }
284
+ async relevant(params) {
285
+ return this.client.get("/v1/corrections/relevant", {
286
+ include_global: false,
287
+ limit: 10,
288
+ ...params
289
+ });
290
+ }
291
+ async get(correctionId) {
292
+ return this.client.get(`/v1/corrections/${correctionId}`);
293
+ }
294
+ async delete(correctionId) {
295
+ return this.client.delete(`/v1/corrections/${correctionId}`);
296
+ }
297
+ };
107
298
  var SearchResource = class extends BaseResource {
108
299
  /**
109
300
  * Search memories
110
301
  */
111
302
  async search(params) {
112
- const response = await this.client.post("/v1/search", {
113
- query: params.query,
114
- collection_id: params.collection_id,
115
- limit: params.limit || 10,
116
- search_type: params.search_type || "hybrid",
117
- filters: params.filters || {}
118
- });
303
+ const response = await this.searchWithProof(params);
119
304
  return response.results;
120
305
  }
121
306
  /** Search while preserving the automatic ProofLoop evidence context. */
122
307
  async searchWithProof(params) {
123
- return this.client.post("/v1/search", {
308
+ const response = await this.client.post("/v1/search", {
124
309
  query: params.query,
125
310
  collection_id: params.collection_id,
126
311
  user_id: params.user_id,
312
+ agent_id: params.agent_id,
313
+ run_id: params.run_id,
127
314
  limit: params.limit || 10,
128
315
  search_type: params.search_type || "hybrid",
129
- filters: params.filters || {}
316
+ filters: params.filters || {},
317
+ fast: params.fast,
318
+ threshold: params.threshold,
319
+ include_low_confidence: params.include_low_confidence ?? false,
320
+ group_by_source: params.group_by_source ?? true,
321
+ debug: params.debug ?? false
130
322
  });
323
+ return enforceSearchSafety(response);
131
324
  }
132
325
  /**
133
326
  * Find similar memories
@@ -143,12 +336,20 @@ var SearchResource = class extends BaseResource {
143
336
  * Perform reasoning over memories
144
337
  */
145
338
  async reason(params) {
146
- return this.client.post("/v1/search/reason", {
147
- query: params.query,
148
- collection_id: params.collection_id,
149
- provider: params.provider,
150
- include_steps: params.include_steps || false
151
- });
339
+ const response = await this.client.post(
340
+ "/v1/search/reason",
341
+ {
342
+ query: params.query,
343
+ collection_id: params.collection_id,
344
+ provider: params.provider,
345
+ include_steps: params.include_steps ?? false,
346
+ user_id: params.user_id,
347
+ agent_id: params.agent_id,
348
+ run_id: params.run_id,
349
+ facets: params.facets ?? []
350
+ }
351
+ );
352
+ return enforceSearchSafety(response, "sources");
152
353
  }
153
354
  };
154
355
  var ProofLoopResource = class extends BaseResource {
@@ -168,11 +369,37 @@ var ProofLoopResource = class extends BaseResource {
168
369
  ...body
169
370
  });
170
371
  }
372
+ async getDecision(decisionId) {
373
+ return this.client.get(`/v1/learning/decisions/${decisionId}`);
374
+ }
375
+ async defineMetric(params) {
376
+ return this.client.post("/v1/learning/metrics", params);
377
+ }
378
+ async listMetrics(params = {}) {
379
+ return this.client.get("/v1/learning/metrics", params);
380
+ }
381
+ async policyInsights(policyKey, params = {}) {
382
+ return this.client.get(`/v1/learning/policies/${policyKey}/insights`, {
383
+ collection_id: params.collection_id,
384
+ user_id: params.user_id,
385
+ action_key: params.action_keys,
386
+ context: params.context ? JSON.stringify(params.context) : void 0
387
+ });
388
+ }
389
+ async evaluatePolicy(policyKey, params = {}) {
390
+ return this.client.post(`/v1/learning/policies/${policyKey}/evaluate`, {
391
+ collection_id: params.collection_id,
392
+ user_id: params.user_id,
393
+ limit: params.limit ?? 500
394
+ });
395
+ }
171
396
  async proof(decisionId) {
172
397
  return this.client.get(`/v1/learning/decisions/${decisionId}/proof`);
173
398
  }
174
- async publicKey() {
175
- return this.client.get("/v1/learning/proof-key");
399
+ async publicKey(keyId) {
400
+ return this.client.get("/v1/learning/proof-key", {
401
+ key_id: keyId
402
+ });
176
403
  }
177
404
  };
178
405
  var RLResource = class extends BaseResource {
@@ -213,56 +440,78 @@ var RLResource = class extends BaseResource {
213
440
  }
214
441
  };
215
442
  var ProceduralResource = class extends BaseResource {
443
+ unwrapProcedure(response) {
444
+ if (response?.procedure) return response.procedure;
445
+ if (response?.procedure_id && !response?.id) {
446
+ return { ...response, id: response.procedure_id };
447
+ }
448
+ return response;
449
+ }
216
450
  /**
217
451
  * Create a new procedure
218
452
  */
219
453
  async create(params) {
220
- return this.client.post("/procedural", {
454
+ const response = await this.client.post("/v1/procedures", {
221
455
  name: params.name,
222
456
  description: params.description,
223
- trigger_condition: params.trigger_condition,
224
- action_sequence: params.action_sequence,
457
+ condition: { expression: params.trigger_condition },
458
+ action: { steps: params.action_sequence },
225
459
  collection_id: params.collection_id,
226
460
  category: params.category,
227
- metadata: params.metadata || {}
461
+ parameters: params.metadata || {}
228
462
  });
463
+ return this.unwrapProcedure(response);
229
464
  }
230
465
  /**
231
466
  * List procedures
232
467
  */
233
468
  async list(params) {
234
- return this.client.get("/procedural", {
469
+ const response = await this.client.get("/v1/procedures", {
235
470
  collection_id: params?.collection_id,
236
471
  category: params?.category,
237
472
  skip: params?.skip || 0,
238
473
  limit: params?.limit || 100
239
474
  });
475
+ return Array.isArray(response) ? response : response?.procedures || [];
240
476
  }
241
477
  /**
242
478
  * Get a specific procedure
243
479
  */
244
480
  async get(procedureId) {
245
- return this.client.get(`/procedural/${procedureId}`);
481
+ const response = await this.client.get(`/v1/procedures/${procedureId}`);
482
+ return this.unwrapProcedure(response);
246
483
  }
247
484
  /**
248
485
  * Execute a procedure
249
486
  */
250
487
  async execute(procedureId, context) {
251
- return this.client.post(`/procedural/${procedureId}/execute`, {
252
- context: context || {}
488
+ const response = await this.client.post(`/v1/procedures/${procedureId}/execute`, {
489
+ input_state: context || {}
253
490
  });
491
+ return response?.execution_result || response;
254
492
  }
255
493
  /**
256
494
  * Update a procedure
257
495
  */
258
496
  async update(procedureId, params) {
259
- return this.client.patch(`/procedural/${procedureId}`, params);
497
+ const body = {};
498
+ if (params.name !== void 0) body.name = params.name;
499
+ if (params.description !== void 0) body.description = params.description;
500
+ if (params.trigger_condition !== void 0) {
501
+ body.condition = { expression: params.trigger_condition };
502
+ }
503
+ if (params.action_sequence !== void 0) {
504
+ body.action = { steps: params.action_sequence };
505
+ }
506
+ if (params.metadata !== void 0) body.parameters = params.metadata;
507
+ const response = await this.client.patch(`/v1/procedures/${procedureId}`, body);
508
+ return this.unwrapProcedure(response);
260
509
  }
261
510
  /**
262
511
  * Delete a procedure
263
512
  */
264
513
  async delete(procedureId) {
265
- await this.client.delete(`/procedural/${procedureId}`);
514
+ await this.client.delete(`/v1/procedures/${procedureId}`);
266
515
  }
267
516
  };
268
517
  var TemporalResource = class extends BaseResource {
@@ -301,6 +550,10 @@ var TemporalResource = class extends BaseResource {
301
550
  entity
302
551
  });
303
552
  }
553
+ /** Permanently delete a tenant-scoped temporal fact by stable ID. */
554
+ async deleteFact(factId) {
555
+ return this.client.delete(`/temporal/facts/${factId}`);
556
+ }
304
557
  };
305
558
  var WorkingMemoryResource = class extends BaseResource {
306
559
  /**
@@ -469,6 +722,8 @@ var MemoryClient = class {
469
722
  this.auth = new AuthResource(this);
470
723
  this.collections = new CollectionsResource(this);
471
724
  this.memories = new MemoriesResource(this);
725
+ this.memoryJobs = new MemoryJobsResource(this);
726
+ this.corrections = new CorrectionsResource(this);
472
727
  this.searchResource = new SearchResource(this);
473
728
  this.rl = new RLResource(this);
474
729
  this.procedural = new ProceduralResource(this);
@@ -482,7 +737,7 @@ var MemoryClient = class {
482
737
  getHeaders() {
483
738
  const headers = {
484
739
  "Content-Type": "application/json",
485
- "User-Agent": "hebbrix-typescript/2.1.0"
740
+ "User-Agent": "hebbrix-typescript/2.2.1"
486
741
  };
487
742
  if (this.apiKey) {
488
743
  headers["Authorization"] = `Bearer ${this.apiKey}`;
@@ -491,7 +746,8 @@ var MemoryClient = class {
491
746
  }
492
747
  handleError(response, data) {
493
748
  const statusCode = response.status;
494
- const message = data?.error?.message || data?.detail || response.statusText;
749
+ const detail = data?.detail;
750
+ const message = data?.error?.message || (typeof detail === "string" ? detail : detail?.message) || response.statusText;
495
751
  if (statusCode === 401) {
496
752
  throw new AuthenticationError(message);
497
753
  } else if (statusCode === 404) {
@@ -509,21 +765,29 @@ var MemoryClient = class {
509
765
  }
510
766
  async request(method, path, options = {}) {
511
767
  const url = `${this.baseUrl}${path}`;
768
+ const { headers: requestHeaders, signal: requestSignal, ...requestOptions } = options;
512
769
  const response = await fetch(url, {
770
+ ...requestOptions,
513
771
  method,
514
772
  headers: {
515
773
  ...this.getHeaders(),
516
- ...options.headers || {}
774
+ ...requestHeaders || {}
517
775
  },
518
- ...options,
519
- signal: AbortSignal.timeout(this.timeout)
776
+ signal: requestSignal || AbortSignal.timeout(this.timeout)
520
777
  });
521
- let data;
778
+ let data = void 0;
522
779
  const contentType = response.headers.get("content-type");
523
- if (contentType?.includes("application/json")) {
524
- data = await response.json();
525
- } else {
526
- data = await response.text();
780
+ const responseText = await response.text();
781
+ if (responseText) {
782
+ if (contentType?.includes("application/json")) {
783
+ try {
784
+ data = JSON.parse(responseText);
785
+ } catch {
786
+ data = { detail: responseText };
787
+ }
788
+ } else {
789
+ data = responseText;
790
+ }
527
791
  }
528
792
  if (!response.ok) {
529
793
  this.handleError(response, data);
@@ -536,7 +800,11 @@ var MemoryClient = class {
536
800
  const searchParams = new URLSearchParams();
537
801
  Object.entries(params).forEach(([key, value]) => {
538
802
  if (value !== void 0 && value !== null) {
539
- searchParams.append(key, String(value));
803
+ if (Array.isArray(value)) {
804
+ value.forEach((item) => searchParams.append(key, String(item)));
805
+ } else {
806
+ searchParams.append(key, String(value));
807
+ }
540
808
  }
541
809
  });
542
810
  url += `?${searchParams.toString()}`;
@@ -572,9 +840,11 @@ export {
572
840
  AuthenticationError,
573
841
  CollectionsResource,
574
842
  ConsolidationResource,
843
+ CorrectionsResource,
575
844
  HebbrixError,
576
845
  MemoriesResource,
577
846
  MemoryClient,
847
+ MemoryJobsResource,
578
848
  MemoryToolsResource,
579
849
  NotFoundError,
580
850
  ProceduralResource,
@@ -586,5 +856,6 @@ export {
586
856
  TemporalResource,
587
857
  ValidationError,
588
858
  WorkingMemoryResource,
589
- WorldModelResource
859
+ WorldModelResource,
860
+ enforceSearchSafety
590
861
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hebbrix",
3
- "version": "2.1.0",
3
+ "version": "2.2.1",
4
4
  "description": "Advanced Memory API for AI Agents with Reinforcement Learning - TypeScript/JavaScript SDK",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -14,12 +14,13 @@
14
14
  },
15
15
  "files": [
16
16
  "dist",
17
- "README.md"
17
+ "README.md",
18
+ "CHANGELOG.md"
18
19
  ],
19
20
  "scripts": {
20
21
  "build": "tsup src/index.ts --format cjs,esm --dts",
21
22
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
22
- "test": "jest",
23
+ "test": "npm run build && node --test tests/*.test.mjs",
23
24
  "lint": "eslint src --ext .ts",
24
25
  "format": "prettier --write \"src/**/*.ts\"",
25
26
  "prepublishOnly": "npm run build"