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.js CHANGED
@@ -27,7 +27,10 @@ __export(index_exports, {
27
27
  CorrectionsResource: () => CorrectionsResource,
28
28
  EntitlementError: () => EntitlementError,
29
29
  HebbrixError: () => HebbrixError,
30
+ IndexingAbortedError: () => IndexingAbortedError,
31
+ IndexingTerminalError: () => IndexingTerminalError,
30
32
  IndexingTimeoutError: () => IndexingTimeoutError,
33
+ IndexingWaitError: () => IndexingWaitError,
31
34
  MemoriesResource: () => MemoriesResource,
32
35
  MemoryClient: () => MemoryClient,
33
36
  MemoryJobsResource: () => MemoryJobsResource,
@@ -58,7 +61,7 @@ var REQUIRED_FIELDS = [
58
61
  function enforceSearchSafety(response, rowsKey = "results") {
59
62
  const data = { ...response };
60
63
  const rawRows = data[rowsKey];
61
- const rows = Array.isArray(rawRows) ? rawRows.filter((row) => typeof row === "object" && row !== null) : [];
64
+ const rows = Array.isArray(rawRows) ? rawRows : [];
62
65
  const missing = REQUIRED_FIELDS.filter(
63
66
  (field) => !Object.prototype.hasOwnProperty.call(data, field)
64
67
  );
@@ -73,6 +76,22 @@ function enforceSearchSafety(response, rowsKey = "results") {
73
76
  reason = "invalid_grounding_receipt";
74
77
  } else if (!Array.isArray(data.evidence_ids)) {
75
78
  reason = "invalid_evidence_ids";
79
+ } else if (data.safety_contract_version !== "search-safety-v1") {
80
+ reason = "unsupported_safety_contract_version";
81
+ } else if (!Array.isArray(rawRows)) {
82
+ reason = "invalid_evidence_rows";
83
+ } else if (data.evidence_ids.some((id) => typeof id !== "string" || !id.trim())) {
84
+ reason = "invalid_evidence_ids";
85
+ } else if (new Set(data.evidence_ids).size !== data.evidence_ids.length) {
86
+ reason = "duplicate_evidence_ids";
87
+ } else if (rows.some((row) => {
88
+ if (!row || typeof row !== "object" || Array.isArray(row)) return true;
89
+ const id = "memory_id" in row ? row.memory_id : row.id;
90
+ return typeof id !== "string" || !id.trim() || "memory_id" in row && "id" in row && row.memory_id !== row.id;
91
+ })) {
92
+ reason = "invalid_evidence_row_identity";
93
+ } else if (rows.length === 0 && data.no_match === false) {
94
+ reason = "no_evidence_rows";
76
95
  } else {
77
96
  const evidenceIds = new Set(data.evidence_ids.map(String));
78
97
  const rowIds = rows.map((row) => row.memory_id ?? row.id).filter((value) => typeof value === "string" && value.length > 0);
@@ -90,6 +109,10 @@ function enforceSearchSafety(response, rowsKey = "results") {
90
109
  data.query_confidence = 0;
91
110
  data.evidence_ids = [];
92
111
  data.evidence_claims = [];
112
+ if (rowsKey === "sources") {
113
+ data.answer = null;
114
+ data.citations = [];
115
+ }
93
116
  if (reason) {
94
117
  data.sdk_safety_reason = reason;
95
118
  data.grounding = { status: "no_grounded_match", reason };
@@ -119,16 +142,72 @@ var EntitlementError = class _EntitlementError extends HebbrixError {
119
142
  Object.setPrototypeOf(this, _EntitlementError.prototype);
120
143
  }
121
144
  };
122
- var IndexingTimeoutError = class _IndexingTimeoutError extends Error {
123
- constructor(message, receipt) {
145
+ var IndexingWaitError = class _IndexingWaitError extends Error {
146
+ constructor(message, receipt, options = {}) {
124
147
  super(message);
125
- this.name = "IndexingTimeoutError";
148
+ this.name = "IndexingWaitError";
126
149
  this.receipt = { ...receipt };
127
- this.memoryIds = [...receipt.memory_ids || []];
128
- this.statusUrl = receipt.status_url;
150
+ this.memoryIds = [
151
+ ...new Set(
152
+ [
153
+ ...receipt.memory_ids || [],
154
+ receipt.memory_id,
155
+ receipt.id,
156
+ ...(receipt.results || []).map(
157
+ (row) => row.memory_id || row.id
158
+ )
159
+ ].filter(Boolean)
160
+ )
161
+ ];
162
+ this.jobId = receipt.job_id;
163
+ 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);
164
+ this.requestId = receipt.request_id;
165
+ this.outboxEventId = receipt.outbox_event_id;
166
+ this.indexingEventId = receipt.indexing_event_id || this.outboxEventId;
167
+ this.eventId = receipt.event_id || this.indexingEventId;
168
+ this.idempotencyReplay = receipt.idempotency_replay;
169
+ this.idempotencyKey = options.idempotencyKey || receipt.idempotency_key;
170
+ this.retryAfter = receipt.retry_after;
171
+ this.recovery = Object.fromEntries(
172
+ Object.entries({
173
+ memory_ids: this.memoryIds,
174
+ job_id: this.jobId,
175
+ status_url: this.statusUrl,
176
+ request_id: this.requestId,
177
+ outbox_event_id: this.outboxEventId,
178
+ indexing_event_id: this.indexingEventId,
179
+ event_id: this.eventId,
180
+ idempotency_key: this.idempotencyKey,
181
+ idempotency_replay: this.idempotencyReplay,
182
+ retry_after: this.retryAfter
183
+ }).filter(([, value]) => value !== void 0 && value !== null)
184
+ );
185
+ this.cause = options.cause;
186
+ Object.setPrototypeOf(this, _IndexingWaitError.prototype);
187
+ }
188
+ };
189
+ var IndexingTimeoutError = class _IndexingTimeoutError extends IndexingWaitError {
190
+ constructor(message, receipt, options = {}) {
191
+ super(message, receipt, options);
192
+ this.name = "IndexingTimeoutError";
129
193
  Object.setPrototypeOf(this, _IndexingTimeoutError.prototype);
130
194
  }
131
195
  };
196
+ var IndexingAbortedError = class _IndexingAbortedError extends IndexingWaitError {
197
+ constructor(message, receipt, options = {}) {
198
+ super(message, receipt, options);
199
+ this.name = "IndexingAbortedError";
200
+ Object.setPrototypeOf(this, _IndexingAbortedError.prototype);
201
+ }
202
+ };
203
+ var IndexingTerminalError = class _IndexingTerminalError extends IndexingWaitError {
204
+ constructor(message, receipt, processingStatus, options = {}) {
205
+ super(message, receipt, options);
206
+ this.name = "IndexingTerminalError";
207
+ this.processingStatus = processingStatus;
208
+ Object.setPrototypeOf(this, _IndexingTerminalError.prototype);
209
+ }
210
+ };
132
211
  var AuthenticationError = class _AuthenticationError extends HebbrixError {
133
212
  constructor(message = "Authentication failed", options = {}) {
134
213
  super(message, 401, options);
@@ -242,22 +321,195 @@ var CollectionsResource = class extends BaseResource {
242
321
  };
243
322
  var MemoriesResource = class extends BaseResource {
244
323
  /**
245
- * Create a new memory
324
+ * Create one logical memory write. When `wait_for_index=true`, a durable
325
+ * pending receipt is polled without issuing a second create request.
246
326
  */
247
327
  async create(params) {
248
328
  if (!params.content?.trim() && !params.messages?.length) {
249
329
  throw new TypeError("content or messages must be provided");
250
330
  }
251
- const { idempotency_key, ...input } = params;
252
- return this.client.request("POST", "/v1/memories", {
253
- headers: idempotency_key ? { "Idempotency-Key": idempotency_key } : void 0,
254
- body: JSON.stringify({
255
- source_type: "text",
256
- metadata: {},
257
- infer: false,
258
- wait_for_index: false,
259
- ...input
260
- })
331
+ const {
332
+ idempotency_key,
333
+ signal,
334
+ index_timeout_ms,
335
+ index_poll_interval_ms,
336
+ ...input
337
+ } = params;
338
+ const receipt = await this.client.request(
339
+ "POST",
340
+ "/v1/memories",
341
+ {
342
+ headers: idempotency_key ? { "Idempotency-Key": idempotency_key } : void 0,
343
+ body: JSON.stringify({
344
+ source_type: "text",
345
+ metadata: {},
346
+ infer: false,
347
+ wait_for_index: false,
348
+ ...input
349
+ }),
350
+ signal
351
+ }
352
+ );
353
+ if (params.wait_for_index && !this.isSearchableCompletion(receipt)) {
354
+ return this.waitForSearchable(receipt, {
355
+ timeoutMs: index_timeout_ms,
356
+ pollIntervalMs: index_poll_interval_ms,
357
+ signal,
358
+ idempotencyKey: idempotency_key
359
+ });
360
+ }
361
+ return receipt;
362
+ }
363
+ /** Poll a single-write durable receipt until every accepted memory is searchable. */
364
+ async waitForSearchable(receipt, options = {}) {
365
+ if (this.isSearchableCompletion(receipt)) {
366
+ return receipt;
367
+ }
368
+ if (this.isTerminalStatus(receipt.processing_status)) {
369
+ const status = String(receipt.processing_status).toLowerCase();
370
+ throw new IndexingTerminalError(
371
+ `memory indexing reached terminal state ${status}`,
372
+ receipt,
373
+ status,
374
+ { idempotencyKey: options.idempotencyKey }
375
+ );
376
+ }
377
+ const ids = this.memoryIds(receipt);
378
+ if (!ids.length) {
379
+ throw new Error("memory receipt does not contain a durable memory id");
380
+ }
381
+ const timeoutMs = Math.max(0, options.timeoutMs ?? 6e4);
382
+ const pollIntervalMs = Math.max(50, options.pollIntervalMs ?? 500);
383
+ const deadline = Date.now() + timeoutMs;
384
+ const paths = ids.map(
385
+ (id, index) => index === 0 && receipt.status_url ? this.readinessPath(receipt.status_url, id) : `/v1/memories/${encodeURIComponent(id)}`
386
+ );
387
+ while (true) {
388
+ this.throwIfPollingAborted(receipt, options);
389
+ if (Date.now() >= deadline) {
390
+ throw new IndexingTimeoutError(
391
+ `memory was not searchable within ${timeoutMs}ms; the write is durable`,
392
+ receipt,
393
+ { idempotencyKey: options.idempotencyKey }
394
+ );
395
+ }
396
+ let rows;
397
+ try {
398
+ rows = await Promise.all(
399
+ paths.map(
400
+ (path) => this.client.request("GET", path, {
401
+ signal: options.signal
402
+ })
403
+ )
404
+ );
405
+ } catch (error) {
406
+ if (options.signal?.aborted) {
407
+ throw new IndexingAbortedError(
408
+ "memory readiness polling was aborted after the write became durable",
409
+ receipt,
410
+ { idempotencyKey: options.idempotencyKey, cause: error }
411
+ );
412
+ }
413
+ throw error;
414
+ }
415
+ const terminal = rows.find(
416
+ (row) => this.isTerminalStatus(row.processing_status)
417
+ );
418
+ if (terminal) {
419
+ const status = String(terminal.processing_status).toLowerCase();
420
+ throw new IndexingTerminalError(
421
+ `memory ${terminal.id} indexing reached terminal state ${status}`,
422
+ receipt,
423
+ status,
424
+ { idempotencyKey: options.idempotencyKey }
425
+ );
426
+ }
427
+ if (rows.every(
428
+ (row) => row.searchable === true && String(row.processing_status || "").toLowerCase() === "completed"
429
+ )) {
430
+ return {
431
+ ...receipt,
432
+ processing_status: "completed",
433
+ searchable: true,
434
+ results: receipt.results.map((result) => ({
435
+ ...result,
436
+ processing_status: "completed"
437
+ }))
438
+ };
439
+ }
440
+ const remainingMs = deadline - Date.now();
441
+ if (remainingMs <= 0) {
442
+ continue;
443
+ }
444
+ await this.pollingDelay(
445
+ Math.min(pollIntervalMs, remainingMs),
446
+ receipt,
447
+ options
448
+ );
449
+ }
450
+ }
451
+ memoryIds(receipt) {
452
+ return [
453
+ ...new Set(
454
+ (receipt.results || []).map((row) => row.memory_id || row.id).filter((id) => Boolean(id))
455
+ )
456
+ ];
457
+ }
458
+ isSearchableCompletion(receipt) {
459
+ return receipt.searchable === true && String(receipt.processing_status || "").toLowerCase() === "completed";
460
+ }
461
+ isTerminalStatus(status) {
462
+ return ["failed", "cancelled", "canceled"].includes(
463
+ String(status || "").toLowerCase()
464
+ );
465
+ }
466
+ readinessPath(statusUrl, memoryId) {
467
+ try {
468
+ const parsed = new URL(statusUrl, "https://status.hebbrix.invalid");
469
+ if (!["http:", "https:"].includes(parsed.protocol)) {
470
+ return `/v1/memories/${encodeURIComponent(memoryId)}`;
471
+ }
472
+ return `${parsed.pathname}${parsed.search}`;
473
+ } catch {
474
+ return `/v1/memories/${encodeURIComponent(memoryId)}`;
475
+ }
476
+ }
477
+ throwIfPollingAborted(receipt, options) {
478
+ if (options.signal?.aborted) {
479
+ throw new IndexingAbortedError(
480
+ "memory readiness polling was aborted after the write became durable",
481
+ receipt,
482
+ {
483
+ idempotencyKey: options.idempotencyKey,
484
+ cause: options.signal.reason
485
+ }
486
+ );
487
+ }
488
+ }
489
+ async pollingDelay(delayMs, receipt, options) {
490
+ await new Promise((resolve, reject) => {
491
+ const onAbort = () => {
492
+ clearTimeout(timer);
493
+ options.signal?.removeEventListener("abort", onAbort);
494
+ reject(
495
+ new IndexingAbortedError(
496
+ "memory readiness polling was aborted after the write became durable",
497
+ receipt,
498
+ {
499
+ idempotencyKey: options.idempotencyKey,
500
+ cause: options.signal?.reason
501
+ }
502
+ )
503
+ );
504
+ };
505
+ const timer = setTimeout(() => {
506
+ options.signal?.removeEventListener("abort", onAbort);
507
+ resolve();
508
+ }, delayMs);
509
+ options.signal?.addEventListener("abort", onAbort, { once: true });
510
+ if (options.signal?.aborted) {
511
+ onAbort();
512
+ }
261
513
  });
262
514
  }
263
515
  /**
@@ -478,7 +730,7 @@ var SearchResource = class extends BaseResource {
478
730
  }
479
731
  };
480
732
  var ProofLoopResource = class extends BaseResource {
481
- /** Create a causal decision bound to search/chat evidence automatically. */
733
+ /** Record a recommendation; this never authorizes execution or proves causality. */
482
734
  async decide(params) {
483
735
  const context = params.proof_context;
484
736
  const token = typeof context === "string" ? context : context?.token;
@@ -497,6 +749,56 @@ var ProofLoopResource = class extends BaseResource {
497
749
  async getDecision(decisionId) {
498
750
  return this.client.get(`/v1/learning/decisions/${decisionId}`);
499
751
  }
752
+ /** Owner-session administration. Use a separate client for the verifier key. */
753
+ async registerVerifier(params) {
754
+ return this.client.post("/v1/learning/verifiers", params);
755
+ }
756
+ async revokeVerifier(verifierId) {
757
+ return this.client.post(
758
+ `/v1/learning/verifiers/${encodeURIComponent(verifierId)}/revoke`,
759
+ {}
760
+ );
761
+ }
762
+ async createEpisode(params) {
763
+ return this.client.post("/v1/learning/episodes", params);
764
+ }
765
+ async getEpisode(episodeId, offset = 0) {
766
+ return this.client.get(
767
+ `/v1/learning/episodes/${encodeURIComponent(episodeId)}`,
768
+ { offset }
769
+ );
770
+ }
771
+ async closeEpisode(episodeId, status) {
772
+ return this.client.post(
773
+ `/v1/learning/episodes/${encodeURIComponent(episodeId)}/close`,
774
+ { status }
775
+ );
776
+ }
777
+ /** Append an execution claim. This method does not execute a tool. */
778
+ async recordExecution(decisionId, claim) {
779
+ return this.client.post(
780
+ `/v1/learning/decisions/${encodeURIComponent(decisionId)}/executions`,
781
+ claim
782
+ );
783
+ }
784
+ async assessment(decisionId, evidenceOffset = 0) {
785
+ return this.client.get(
786
+ `/v1/learning/decisions/${encodeURIComponent(decisionId)}/assessment`,
787
+ { evidence_offset: evidenceOffset }
788
+ );
789
+ }
790
+ async verifierEvidence(verifierId, decisionId) {
791
+ return this.client.get(
792
+ `/v1/learning/verifiers/${encodeURIComponent(verifierId)}/decisions/${encodeURIComponent(decisionId)}`
793
+ );
794
+ }
795
+ /** Deliver using the dedicated source credential, after checking execution independently. */
796
+ async deliverVerifiedOutcomes(verifierId, delivery) {
797
+ return this.client.post(
798
+ `/v1/learning/verifiers/${encodeURIComponent(verifierId)}/events`,
799
+ delivery
800
+ );
801
+ }
500
802
  async defineMetric(params) {
501
803
  return this.client.post("/v1/learning/metrics", params);
502
804
  }
@@ -608,9 +910,12 @@ var ProceduralResource = class extends BaseResource {
608
910
  * Execute a procedure
609
911
  */
610
912
  async execute(procedureId, context) {
611
- const response = await this.client.post(`/v1/procedures/${procedureId}/execute`, {
612
- input_state: context || {}
613
- });
913
+ const response = await this.client.post(
914
+ `/v1/procedures/${procedureId}/execute`,
915
+ {
916
+ input_state: context || {}
917
+ }
918
+ );
614
919
  return response?.execution_result || response;
615
920
  }
616
921
  /**
@@ -627,7 +932,10 @@ var ProceduralResource = class extends BaseResource {
627
932
  body.action = { steps: params.action_sequence };
628
933
  }
629
934
  if (params.metadata !== void 0) body.parameters = params.metadata;
630
- const response = await this.client.patch(`/v1/procedures/${procedureId}`, body);
935
+ const response = await this.client.patch(
936
+ `/v1/procedures/${procedureId}`,
937
+ body
938
+ );
631
939
  return this.unwrapProcedure(response);
632
940
  }
633
941
  /**
@@ -710,7 +1018,9 @@ var TemporalResource = class extends BaseResource {
710
1018
  */
711
1019
  async pointInTime(timestamp, entity) {
712
1020
  if (!entity) {
713
- throw new TypeError("entity is required and maps to the canonical subject");
1021
+ throw new TypeError(
1022
+ "entity is required and maps to the canonical subject"
1023
+ );
714
1024
  }
715
1025
  return this.queryAtTime({
716
1026
  timestamp,
@@ -794,7 +1104,8 @@ var MemoryToolsResource = class extends BaseResource {
794
1104
  */
795
1105
  async insert(params) {
796
1106
  const metadata = { ...params.metadata || {} };
797
- if (params.position !== void 0) metadata.requested_position = params.position;
1107
+ if (params.position !== void 0)
1108
+ metadata.requested_position = params.position;
798
1109
  if (params.reason !== void 0) metadata.reason = params.reason;
799
1110
  return this.client.post("/v1/memory-tools/insert", {
800
1111
  collection_id: params.collection_id,
@@ -838,7 +1149,7 @@ var MemoryClient = class {
838
1149
  getHeaders() {
839
1150
  const headers = {
840
1151
  "Content-Type": "application/json",
841
- "User-Agent": "hebbrix-typescript/2.3.0"
1152
+ "User-Agent": "hebbrix-typescript/2.4.0"
842
1153
  };
843
1154
  if (this.apiKey) {
844
1155
  headers["Authorization"] = `Bearer ${this.apiKey}`;
@@ -864,7 +1175,9 @@ var MemoryClient = class {
864
1175
  throw new RateLimitError(message, { code, requestId, details });
865
1176
  } else if (statusCode >= 500) {
866
1177
  throw new ServerError(message, { code, requestId, details });
867
- } else if ((statusCode === 402 || statusCode === 403) && (String(code || "").includes("ENTITLEMENT") || ["feature_not_available", "tier_upgrade_required"].includes(details?.error))) {
1178
+ } else if ((statusCode === 402 || statusCode === 403) && (String(code || "").includes("ENTITLEMENT") || ["feature_not_available", "tier_upgrade_required"].includes(
1179
+ details?.error
1180
+ ))) {
868
1181
  throw new EntitlementError(message, statusCode, {
869
1182
  code,
870
1183
  requestId,
@@ -876,7 +1189,11 @@ var MemoryClient = class {
876
1189
  }
877
1190
  async request(method, path, options = {}) {
878
1191
  const url = `${this.baseUrl}${path}`;
879
- const { headers: requestHeaders, signal: requestSignal, ...requestOptions } = options;
1192
+ const {
1193
+ headers: requestHeaders,
1194
+ signal: requestSignal,
1195
+ ...requestOptions
1196
+ } = options;
880
1197
  const response = await fetch(url, {
881
1198
  ...requestOptions,
882
1199
  method,
@@ -903,6 +1220,21 @@ var MemoryClient = class {
903
1220
  if (!response.ok) {
904
1221
  this.handleError(response, data);
905
1222
  }
1223
+ if (data && typeof data === "object" && !Array.isArray(data)) {
1224
+ const requestId = response.headers.get("X-Request-ID");
1225
+ const statusUrl = response.headers.get("Location");
1226
+ const outboxEventId = response.headers.get("X-Hebbrix-Index-Event");
1227
+ const retryAfter = response.headers.get("Retry-After");
1228
+ const idempotencyReplay = response.headers.get("X-Idempotent-Replay");
1229
+ data = {
1230
+ ...data,
1231
+ ...data.request_id === void 0 && requestId ? { request_id: requestId } : {},
1232
+ ...data.status_url === void 0 && statusUrl ? { status_url: statusUrl } : {},
1233
+ ...data.outbox_event_id === void 0 && outboxEventId ? { outbox_event_id: outboxEventId } : {},
1234
+ ...data.retry_after === void 0 && retryAfter ? { retry_after: retryAfter } : {},
1235
+ ...data.idempotency_replay === void 0 && idempotencyReplay ? { idempotency_replay: idempotencyReplay.toLowerCase() === "true" } : {}
1236
+ };
1237
+ }
906
1238
  return data;
907
1239
  }
908
1240
  async get(path, params) {
@@ -955,7 +1287,10 @@ var MemoryClient = class {
955
1287
  CorrectionsResource,
956
1288
  EntitlementError,
957
1289
  HebbrixError,
1290
+ IndexingAbortedError,
1291
+ IndexingTerminalError,
958
1292
  IndexingTimeoutError,
1293
+ IndexingWaitError,
959
1294
  MemoriesResource,
960
1295
  MemoryClient,
961
1296
  MemoryJobsResource,