@pentatonic-ai/ai-agent-sdk 0.5.9 → 0.5.10

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.5.9",
3
+ "version": "0.5.10",
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",
@@ -709,6 +709,37 @@ describe("ingest dedup option", () => {
709
709
  expect(inserted[0].client_id).toBe("c");
710
710
  });
711
711
 
712
+ it("opts.dedupContent: matches against the raw form, stores the wrapped form", async () => {
713
+ const { db, inserted } = makeMockDb({
714
+ existing: [
715
+ // Row was stored on a previous run with a 10:00 timestamp prefix
716
+ {
717
+ id: "mem_legacy",
718
+ client_id: "c",
719
+ content: "[2026-04-26T10:00:00Z] Caroline went to a support group",
720
+ },
721
+ ],
722
+ });
723
+
724
+ const out = await ingest(
725
+ db,
726
+ mockAi,
727
+ mockLlm,
728
+ // The retry would store with a fresh timestamp — strict-equality
729
+ // would miss the dup. dedupContent makes us match on the raw form.
730
+ "[2026-04-26T10:00:01Z] Caroline went to a support group",
731
+ {
732
+ clientId: "c",
733
+ dedup: true,
734
+ dedupContent: "Caroline went to a support group",
735
+ }
736
+ );
737
+
738
+ expect(out.deduped).toBe(true);
739
+ expect(out.id).toBe("mem_legacy");
740
+ expect(inserted).toHaveLength(0);
741
+ });
742
+
712
743
  it("dedup check failure falls through to insert (best-effort semantics)", async () => {
713
744
  let dupCheckSql = null;
714
745
  const flakyDb = async (sql, params) => {
@@ -31,6 +31,12 @@ import { distill } from "./distill.js";
31
31
  * `dedup:false` behaviour). The eventual structural fix is a
32
32
  * `UNIQUE(client_id, content_hash)` constraint at the schema level;
33
33
  * this option is the bridge.
34
+ * @param {string} [opts.dedupContent] - Optional: the string to dedup
35
+ * against, when it differs from what gets stored. Use when callers
36
+ * wrap the stored content in a non-stable prefix (timestamps, run
37
+ * ids) — pass the raw form here so retries of the same logical event
38
+ * match across runs whose prefixes differ by a few ms. Defaults to
39
+ * `content`.
34
40
  * @returns {Promise<{id: string, content: string, layerId: string, deduped?: boolean}>}
35
41
  */
36
42
  export async function ingest(db, ai, llm, content, opts = {}) {
@@ -54,17 +60,21 @@ export async function ingest(db, ai, llm, content, opts = {}) {
54
60
 
55
61
  // Optional dedup: skip the insert (and all the embedding/HyDE/distill
56
62
  // work that would follow) if a row with byte-equal content already
57
- // exists for this tenant. The OR-LIKE branch matches against the
58
- // legacy `[<iso>] <content>` form so callers that wrote with a
63
+ // exists for this tenant. The dedup key is `opts.dedupContent` if
64
+ // provided (use for callers that wrap the stored form in a non-stable
65
+ // prefix like a timestamp), else `content`. The OR-LIKE branch matches
66
+ // against legacy `[<iso>] <content>` rows so callers that wrote with a
59
67
  // timestamp prefix dedup correctly until the legacy corpus ages out.
60
68
  if (opts.dedup) {
69
+ const dedupKey =
70
+ typeof opts.dedupContent === "string" ? opts.dedupContent : content;
61
71
  try {
62
72
  const dupCheck = await db(
63
73
  `SELECT id FROM memory_nodes
64
74
  WHERE client_id = $1
65
75
  AND (content = $2 OR content LIKE '%] ' || $2)
66
76
  LIMIT 1`,
67
- [clientId, content]
77
+ [clientId, dedupKey]
68
78
  );
69
79
  if (dupCheck.rows?.length) {
70
80
  log(`dedup: matched existing memory ${dupCheck.rows[0].id}`);