hebbrix 2.3.0 → 2.4.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/dist/index.mjs CHANGED
@@ -10,7 +10,7 @@ var REQUIRED_FIELDS = [
10
10
  function enforceSearchSafety(response, rowsKey = "results") {
11
11
  const data = { ...response };
12
12
  const rawRows = data[rowsKey];
13
- const rows = Array.isArray(rawRows) ? rawRows.filter((row) => typeof row === "object" && row !== null) : [];
13
+ const rows = Array.isArray(rawRows) ? rawRows : [];
14
14
  const missing = REQUIRED_FIELDS.filter(
15
15
  (field) => !Object.prototype.hasOwnProperty.call(data, field)
16
16
  );
@@ -25,6 +25,22 @@ function enforceSearchSafety(response, rowsKey = "results") {
25
25
  reason = "invalid_grounding_receipt";
26
26
  } else if (!Array.isArray(data.evidence_ids)) {
27
27
  reason = "invalid_evidence_ids";
28
+ } else if (data.safety_contract_version !== "search-safety-v1") {
29
+ reason = "unsupported_safety_contract_version";
30
+ } else if (!Array.isArray(rawRows)) {
31
+ reason = "invalid_evidence_rows";
32
+ } else if (data.evidence_ids.some((id) => typeof id !== "string" || !id.trim())) {
33
+ reason = "invalid_evidence_ids";
34
+ } else if (new Set(data.evidence_ids).size !== data.evidence_ids.length) {
35
+ reason = "duplicate_evidence_ids";
36
+ } else if (rows.some((row) => {
37
+ if (!row || typeof row !== "object" || Array.isArray(row)) return true;
38
+ const id = "memory_id" in row ? row.memory_id : row.id;
39
+ return typeof id !== "string" || !id.trim() || "memory_id" in row && "id" in row && row.memory_id !== row.id;
40
+ })) {
41
+ reason = "invalid_evidence_row_identity";
42
+ } else if (rows.length === 0 && data.no_match === false) {
43
+ reason = "no_evidence_rows";
28
44
  } else {
29
45
  const evidenceIds = new Set(data.evidence_ids.map(String));
30
46
  const rowIds = rows.map((row) => row.memory_id ?? row.id).filter((value) => typeof value === "string" && value.length > 0);
@@ -42,6 +58,10 @@ function enforceSearchSafety(response, rowsKey = "results") {
42
58
  data.query_confidence = 0;
43
59
  data.evidence_ids = [];
44
60
  data.evidence_claims = [];
61
+ if (rowsKey === "sources") {
62
+ data.answer = null;
63
+ data.citations = [];
64
+ }
45
65
  if (reason) {
46
66
  data.sdk_safety_reason = reason;
47
67
  data.grounding = { status: "no_grounded_match", reason };
@@ -71,16 +91,72 @@ var EntitlementError = class _EntitlementError extends HebbrixError {
71
91
  Object.setPrototypeOf(this, _EntitlementError.prototype);
72
92
  }
73
93
  };
74
- var IndexingTimeoutError = class _IndexingTimeoutError extends Error {
75
- constructor(message, receipt) {
94
+ var IndexingWaitError = class _IndexingWaitError extends Error {
95
+ constructor(message, receipt, options = {}) {
76
96
  super(message);
77
- this.name = "IndexingTimeoutError";
97
+ this.name = "IndexingWaitError";
78
98
  this.receipt = { ...receipt };
79
- this.memoryIds = [...receipt.memory_ids || []];
80
- this.statusUrl = receipt.status_url;
99
+ this.memoryIds = [
100
+ ...new Set(
101
+ [
102
+ ...receipt.memory_ids || [],
103
+ receipt.memory_id,
104
+ receipt.id,
105
+ ...(receipt.results || []).map(
106
+ (row) => row.memory_id || row.id
107
+ )
108
+ ].filter(Boolean)
109
+ )
110
+ ];
111
+ this.jobId = receipt.job_id;
112
+ this.statusUrl = receipt.status_url || (this.memoryIds.length === 1 ? `/v1/memories/${encodeURIComponent(this.memoryIds[0])}` : this.jobId ? `/v1/memory-jobs/${encodeURIComponent(this.jobId)}` : void 0);
113
+ this.requestId = receipt.request_id;
114
+ this.outboxEventId = receipt.outbox_event_id;
115
+ this.indexingEventId = receipt.indexing_event_id || this.outboxEventId;
116
+ this.eventId = receipt.event_id || this.indexingEventId;
117
+ this.idempotencyReplay = receipt.idempotency_replay;
118
+ this.idempotencyKey = options.idempotencyKey || receipt.idempotency_key;
119
+ this.retryAfter = receipt.retry_after;
120
+ this.recovery = Object.fromEntries(
121
+ Object.entries({
122
+ memory_ids: this.memoryIds,
123
+ job_id: this.jobId,
124
+ status_url: this.statusUrl,
125
+ request_id: this.requestId,
126
+ outbox_event_id: this.outboxEventId,
127
+ indexing_event_id: this.indexingEventId,
128
+ event_id: this.eventId,
129
+ idempotency_key: this.idempotencyKey,
130
+ idempotency_replay: this.idempotencyReplay,
131
+ retry_after: this.retryAfter
132
+ }).filter(([, value]) => value !== void 0 && value !== null)
133
+ );
134
+ this.cause = options.cause;
135
+ Object.setPrototypeOf(this, _IndexingWaitError.prototype);
136
+ }
137
+ };
138
+ var IndexingTimeoutError = class _IndexingTimeoutError extends IndexingWaitError {
139
+ constructor(message, receipt, options = {}) {
140
+ super(message, receipt, options);
141
+ this.name = "IndexingTimeoutError";
81
142
  Object.setPrototypeOf(this, _IndexingTimeoutError.prototype);
82
143
  }
83
144
  };
145
+ var IndexingAbortedError = class _IndexingAbortedError extends IndexingWaitError {
146
+ constructor(message, receipt, options = {}) {
147
+ super(message, receipt, options);
148
+ this.name = "IndexingAbortedError";
149
+ Object.setPrototypeOf(this, _IndexingAbortedError.prototype);
150
+ }
151
+ };
152
+ var IndexingTerminalError = class _IndexingTerminalError extends IndexingWaitError {
153
+ constructor(message, receipt, processingStatus, options = {}) {
154
+ super(message, receipt, options);
155
+ this.name = "IndexingTerminalError";
156
+ this.processingStatus = processingStatus;
157
+ Object.setPrototypeOf(this, _IndexingTerminalError.prototype);
158
+ }
159
+ };
84
160
  var AuthenticationError = class _AuthenticationError extends HebbrixError {
85
161
  constructor(message = "Authentication failed", options = {}) {
86
162
  super(message, 401, options);
@@ -194,22 +270,195 @@ var CollectionsResource = class extends BaseResource {
194
270
  };
195
271
  var MemoriesResource = class extends BaseResource {
196
272
  /**
197
- * Create a new memory
273
+ * Create one logical memory write. When `wait_for_index=true`, a durable
274
+ * pending receipt is polled without issuing a second create request.
198
275
  */
199
276
  async create(params) {
200
277
  if (!params.content?.trim() && !params.messages?.length) {
201
278
  throw new TypeError("content or messages must be provided");
202
279
  }
203
- const { idempotency_key, ...input } = params;
204
- return this.client.request("POST", "/v1/memories", {
205
- headers: idempotency_key ? { "Idempotency-Key": idempotency_key } : void 0,
206
- body: JSON.stringify({
207
- source_type: "text",
208
- metadata: {},
209
- infer: false,
210
- wait_for_index: false,
211
- ...input
212
- })
280
+ const {
281
+ idempotency_key,
282
+ signal,
283
+ index_timeout_ms,
284
+ index_poll_interval_ms,
285
+ ...input
286
+ } = params;
287
+ const receipt = await this.client.request(
288
+ "POST",
289
+ "/v1/memories",
290
+ {
291
+ headers: idempotency_key ? { "Idempotency-Key": idempotency_key } : void 0,
292
+ body: JSON.stringify({
293
+ source_type: "text",
294
+ metadata: {},
295
+ infer: false,
296
+ wait_for_index: false,
297
+ ...input
298
+ }),
299
+ signal
300
+ }
301
+ );
302
+ if (params.wait_for_index && !this.isSearchableCompletion(receipt)) {
303
+ return this.waitForSearchable(receipt, {
304
+ timeoutMs: index_timeout_ms,
305
+ pollIntervalMs: index_poll_interval_ms,
306
+ signal,
307
+ idempotencyKey: idempotency_key
308
+ });
309
+ }
310
+ return receipt;
311
+ }
312
+ /** Poll a single-write durable receipt until every accepted memory is searchable. */
313
+ async waitForSearchable(receipt, options = {}) {
314
+ if (this.isSearchableCompletion(receipt)) {
315
+ return receipt;
316
+ }
317
+ if (this.isTerminalStatus(receipt.processing_status)) {
318
+ const status = String(receipt.processing_status).toLowerCase();
319
+ throw new IndexingTerminalError(
320
+ `memory indexing reached terminal state ${status}`,
321
+ receipt,
322
+ status,
323
+ { idempotencyKey: options.idempotencyKey }
324
+ );
325
+ }
326
+ const ids = this.memoryIds(receipt);
327
+ if (!ids.length) {
328
+ throw new Error("memory receipt does not contain a durable memory id");
329
+ }
330
+ const timeoutMs = Math.max(0, options.timeoutMs ?? 6e4);
331
+ const pollIntervalMs = Math.max(50, options.pollIntervalMs ?? 500);
332
+ const deadline = Date.now() + timeoutMs;
333
+ const paths = ids.map(
334
+ (id, index) => index === 0 && receipt.status_url ? this.readinessPath(receipt.status_url, id) : `/v1/memories/${encodeURIComponent(id)}`
335
+ );
336
+ while (true) {
337
+ this.throwIfPollingAborted(receipt, options);
338
+ if (Date.now() >= deadline) {
339
+ throw new IndexingTimeoutError(
340
+ `memory was not searchable within ${timeoutMs}ms; the write is durable`,
341
+ receipt,
342
+ { idempotencyKey: options.idempotencyKey }
343
+ );
344
+ }
345
+ let rows;
346
+ try {
347
+ rows = await Promise.all(
348
+ paths.map(
349
+ (path) => this.client.request("GET", path, {
350
+ signal: options.signal
351
+ })
352
+ )
353
+ );
354
+ } catch (error) {
355
+ if (options.signal?.aborted) {
356
+ throw new IndexingAbortedError(
357
+ "memory readiness polling was aborted after the write became durable",
358
+ receipt,
359
+ { idempotencyKey: options.idempotencyKey, cause: error }
360
+ );
361
+ }
362
+ throw error;
363
+ }
364
+ const terminal = rows.find(
365
+ (row) => this.isTerminalStatus(row.processing_status)
366
+ );
367
+ if (terminal) {
368
+ const status = String(terminal.processing_status).toLowerCase();
369
+ throw new IndexingTerminalError(
370
+ `memory ${terminal.id} indexing reached terminal state ${status}`,
371
+ receipt,
372
+ status,
373
+ { idempotencyKey: options.idempotencyKey }
374
+ );
375
+ }
376
+ if (rows.every(
377
+ (row) => row.searchable === true && String(row.processing_status || "").toLowerCase() === "completed"
378
+ )) {
379
+ return {
380
+ ...receipt,
381
+ processing_status: "completed",
382
+ searchable: true,
383
+ results: receipt.results.map((result) => ({
384
+ ...result,
385
+ processing_status: "completed"
386
+ }))
387
+ };
388
+ }
389
+ const remainingMs = deadline - Date.now();
390
+ if (remainingMs <= 0) {
391
+ continue;
392
+ }
393
+ await this.pollingDelay(
394
+ Math.min(pollIntervalMs, remainingMs),
395
+ receipt,
396
+ options
397
+ );
398
+ }
399
+ }
400
+ memoryIds(receipt) {
401
+ return [
402
+ ...new Set(
403
+ (receipt.results || []).map((row) => row.memory_id || row.id).filter((id) => Boolean(id))
404
+ )
405
+ ];
406
+ }
407
+ isSearchableCompletion(receipt) {
408
+ return receipt.searchable === true && String(receipt.processing_status || "").toLowerCase() === "completed";
409
+ }
410
+ isTerminalStatus(status) {
411
+ return ["failed", "cancelled", "canceled"].includes(
412
+ String(status || "").toLowerCase()
413
+ );
414
+ }
415
+ readinessPath(statusUrl, memoryId) {
416
+ try {
417
+ const parsed = new URL(statusUrl, "https://status.hebbrix.invalid");
418
+ if (!["http:", "https:"].includes(parsed.protocol)) {
419
+ return `/v1/memories/${encodeURIComponent(memoryId)}`;
420
+ }
421
+ return `${parsed.pathname}${parsed.search}`;
422
+ } catch {
423
+ return `/v1/memories/${encodeURIComponent(memoryId)}`;
424
+ }
425
+ }
426
+ throwIfPollingAborted(receipt, options) {
427
+ if (options.signal?.aborted) {
428
+ throw new IndexingAbortedError(
429
+ "memory readiness polling was aborted after the write became durable",
430
+ receipt,
431
+ {
432
+ idempotencyKey: options.idempotencyKey,
433
+ cause: options.signal.reason
434
+ }
435
+ );
436
+ }
437
+ }
438
+ async pollingDelay(delayMs, receipt, options) {
439
+ await new Promise((resolve, reject) => {
440
+ const onAbort = () => {
441
+ clearTimeout(timer);
442
+ options.signal?.removeEventListener("abort", onAbort);
443
+ reject(
444
+ new IndexingAbortedError(
445
+ "memory readiness polling was aborted after the write became durable",
446
+ receipt,
447
+ {
448
+ idempotencyKey: options.idempotencyKey,
449
+ cause: options.signal?.reason
450
+ }
451
+ )
452
+ );
453
+ };
454
+ const timer = setTimeout(() => {
455
+ options.signal?.removeEventListener("abort", onAbort);
456
+ resolve();
457
+ }, delayMs);
458
+ options.signal?.addEventListener("abort", onAbort, { once: true });
459
+ if (options.signal?.aborted) {
460
+ onAbort();
461
+ }
213
462
  });
214
463
  }
215
464
  /**
@@ -430,7 +679,7 @@ var SearchResource = class extends BaseResource {
430
679
  }
431
680
  };
432
681
  var ProofLoopResource = class extends BaseResource {
433
- /** Create a causal decision bound to search/chat evidence automatically. */
682
+ /** Record a recommendation; this never authorizes execution or proves causality. */
434
683
  async decide(params) {
435
684
  const context = params.proof_context;
436
685
  const token = typeof context === "string" ? context : context?.token;
@@ -449,6 +698,56 @@ var ProofLoopResource = class extends BaseResource {
449
698
  async getDecision(decisionId) {
450
699
  return this.client.get(`/v1/learning/decisions/${decisionId}`);
451
700
  }
701
+ /** Owner-session administration. Use a separate client for the verifier key. */
702
+ async registerVerifier(params) {
703
+ return this.client.post("/v1/learning/verifiers", params);
704
+ }
705
+ async revokeVerifier(verifierId) {
706
+ return this.client.post(
707
+ `/v1/learning/verifiers/${encodeURIComponent(verifierId)}/revoke`,
708
+ {}
709
+ );
710
+ }
711
+ async createEpisode(params) {
712
+ return this.client.post("/v1/learning/episodes", params);
713
+ }
714
+ async getEpisode(episodeId, offset = 0) {
715
+ return this.client.get(
716
+ `/v1/learning/episodes/${encodeURIComponent(episodeId)}`,
717
+ { offset }
718
+ );
719
+ }
720
+ async closeEpisode(episodeId, status) {
721
+ return this.client.post(
722
+ `/v1/learning/episodes/${encodeURIComponent(episodeId)}/close`,
723
+ { status }
724
+ );
725
+ }
726
+ /** Append an execution claim. This method does not execute a tool. */
727
+ async recordExecution(decisionId, claim) {
728
+ return this.client.post(
729
+ `/v1/learning/decisions/${encodeURIComponent(decisionId)}/executions`,
730
+ claim
731
+ );
732
+ }
733
+ async assessment(decisionId, evidenceOffset = 0) {
734
+ return this.client.get(
735
+ `/v1/learning/decisions/${encodeURIComponent(decisionId)}/assessment`,
736
+ { evidence_offset: evidenceOffset }
737
+ );
738
+ }
739
+ async verifierEvidence(verifierId, decisionId) {
740
+ return this.client.get(
741
+ `/v1/learning/verifiers/${encodeURIComponent(verifierId)}/decisions/${encodeURIComponent(decisionId)}`
742
+ );
743
+ }
744
+ /** Deliver using the dedicated source credential, after checking execution independently. */
745
+ async deliverVerifiedOutcomes(verifierId, delivery) {
746
+ return this.client.post(
747
+ `/v1/learning/verifiers/${encodeURIComponent(verifierId)}/events`,
748
+ delivery
749
+ );
750
+ }
452
751
  async defineMetric(params) {
453
752
  return this.client.post("/v1/learning/metrics", params);
454
753
  }
@@ -560,9 +859,12 @@ var ProceduralResource = class extends BaseResource {
560
859
  * Execute a procedure
561
860
  */
562
861
  async execute(procedureId, context) {
563
- const response = await this.client.post(`/v1/procedures/${procedureId}/execute`, {
564
- input_state: context || {}
565
- });
862
+ const response = await this.client.post(
863
+ `/v1/procedures/${procedureId}/execute`,
864
+ {
865
+ input_state: context || {}
866
+ }
867
+ );
566
868
  return response?.execution_result || response;
567
869
  }
568
870
  /**
@@ -579,7 +881,10 @@ var ProceduralResource = class extends BaseResource {
579
881
  body.action = { steps: params.action_sequence };
580
882
  }
581
883
  if (params.metadata !== void 0) body.parameters = params.metadata;
582
- const response = await this.client.patch(`/v1/procedures/${procedureId}`, body);
884
+ const response = await this.client.patch(
885
+ `/v1/procedures/${procedureId}`,
886
+ body
887
+ );
583
888
  return this.unwrapProcedure(response);
584
889
  }
585
890
  /**
@@ -662,7 +967,9 @@ var TemporalResource = class extends BaseResource {
662
967
  */
663
968
  async pointInTime(timestamp, entity) {
664
969
  if (!entity) {
665
- throw new TypeError("entity is required and maps to the canonical subject");
970
+ throw new TypeError(
971
+ "entity is required and maps to the canonical subject"
972
+ );
666
973
  }
667
974
  return this.queryAtTime({
668
975
  timestamp,
@@ -746,7 +1053,8 @@ var MemoryToolsResource = class extends BaseResource {
746
1053
  */
747
1054
  async insert(params) {
748
1055
  const metadata = { ...params.metadata || {} };
749
- if (params.position !== void 0) metadata.requested_position = params.position;
1056
+ if (params.position !== void 0)
1057
+ metadata.requested_position = params.position;
750
1058
  if (params.reason !== void 0) metadata.reason = params.reason;
751
1059
  return this.client.post("/v1/memory-tools/insert", {
752
1060
  collection_id: params.collection_id,
@@ -790,7 +1098,7 @@ var MemoryClient = class {
790
1098
  getHeaders() {
791
1099
  const headers = {
792
1100
  "Content-Type": "application/json",
793
- "User-Agent": "hebbrix-typescript/2.3.0"
1101
+ "User-Agent": "hebbrix-typescript/2.4.0"
794
1102
  };
795
1103
  if (this.apiKey) {
796
1104
  headers["Authorization"] = `Bearer ${this.apiKey}`;
@@ -816,7 +1124,9 @@ var MemoryClient = class {
816
1124
  throw new RateLimitError(message, { code, requestId, details });
817
1125
  } else if (statusCode >= 500) {
818
1126
  throw new ServerError(message, { code, requestId, details });
819
- } else if ((statusCode === 402 || statusCode === 403) && (String(code || "").includes("ENTITLEMENT") || ["feature_not_available", "tier_upgrade_required"].includes(details?.error))) {
1127
+ } else if ((statusCode === 402 || statusCode === 403) && (String(code || "").includes("ENTITLEMENT") || ["feature_not_available", "tier_upgrade_required"].includes(
1128
+ details?.error
1129
+ ))) {
820
1130
  throw new EntitlementError(message, statusCode, {
821
1131
  code,
822
1132
  requestId,
@@ -828,7 +1138,11 @@ var MemoryClient = class {
828
1138
  }
829
1139
  async request(method, path, options = {}) {
830
1140
  const url = `${this.baseUrl}${path}`;
831
- const { headers: requestHeaders, signal: requestSignal, ...requestOptions } = options;
1141
+ const {
1142
+ headers: requestHeaders,
1143
+ signal: requestSignal,
1144
+ ...requestOptions
1145
+ } = options;
832
1146
  const response = await fetch(url, {
833
1147
  ...requestOptions,
834
1148
  method,
@@ -855,6 +1169,21 @@ var MemoryClient = class {
855
1169
  if (!response.ok) {
856
1170
  this.handleError(response, data);
857
1171
  }
1172
+ if (data && typeof data === "object" && !Array.isArray(data)) {
1173
+ const requestId = response.headers.get("X-Request-ID");
1174
+ const statusUrl = response.headers.get("Location");
1175
+ const outboxEventId = response.headers.get("X-Hebbrix-Index-Event");
1176
+ const retryAfter = response.headers.get("Retry-After");
1177
+ const idempotencyReplay = response.headers.get("X-Idempotent-Replay");
1178
+ data = {
1179
+ ...data,
1180
+ ...data.request_id === void 0 && requestId ? { request_id: requestId } : {},
1181
+ ...data.status_url === void 0 && statusUrl ? { status_url: statusUrl } : {},
1182
+ ...data.outbox_event_id === void 0 && outboxEventId ? { outbox_event_id: outboxEventId } : {},
1183
+ ...data.retry_after === void 0 && retryAfter ? { retry_after: retryAfter } : {},
1184
+ ...data.idempotency_replay === void 0 && idempotencyReplay ? { idempotency_replay: idempotencyReplay.toLowerCase() === "true" } : {}
1185
+ };
1186
+ }
858
1187
  return data;
859
1188
  }
860
1189
  async get(path, params) {
@@ -906,7 +1235,10 @@ export {
906
1235
  CorrectionsResource,
907
1236
  EntitlementError,
908
1237
  HebbrixError,
1238
+ IndexingAbortedError,
1239
+ IndexingTerminalError,
909
1240
  IndexingTimeoutError,
1241
+ IndexingWaitError,
910
1242
  MemoriesResource,
911
1243
  MemoryClient,
912
1244
  MemoryJobsResource,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hebbrix",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Typed TypeScript client for Hebbrix memory, retrieval, and outcome-learning APIs",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -46,10 +46,14 @@
46
46
  ],
47
47
  "author": "Hebbrix Team <support@hebbrix.com>",
48
48
  "license": "MIT",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/Hebbrix/hebbrix-typescript.git"
52
+ },
49
53
  "bugs": {
50
- "url": "https://www.hebbrix.com/contact"
54
+ "url": "https://github.com/Hebbrix/hebbrix-typescript/issues"
51
55
  },
52
- "homepage": "https://hebbrix.com",
56
+ "homepage": "https://docs.hebbrix.com",
53
57
  "publishConfig": {
54
58
  "access": "public"
55
59
  },