@pentatonic-ai/ai-agent-sdk 0.10.25 → 0.10.27

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.cjs CHANGED
@@ -878,7 +878,7 @@ function fireAndForgetEmit(clientConfig, sessionOpts, messages, result, model) {
878
878
  }
879
879
 
880
880
  // src/telemetry.js
881
- var VERSION = "0.10.25";
881
+ var VERSION = "0.10.27";
882
882
  var TELEMETRY_URL = "https://sdk-telemetry.philip-134.workers.dev";
883
883
  function machineId() {
884
884
  const raw = typeof process !== "undefined" ? `${process.env?.USER || process.env?.USERNAME || "u"}:${process.platform || "x"}:${process.arch || "x"}` : "browser";
package/dist/index.js CHANGED
@@ -847,7 +847,7 @@ function fireAndForgetEmit(clientConfig, sessionOpts, messages, result, model) {
847
847
  }
848
848
 
849
849
  // src/telemetry.js
850
- var VERSION = "0.10.25";
850
+ var VERSION = "0.10.27";
851
851
  var TELEMETRY_URL = "https://sdk-telemetry.philip-134.workers.dev";
852
852
  function machineId() {
853
853
  const raw = typeof process !== "undefined" ? `${process.env?.USER || process.env?.USERNAME || "u"}:${process.platform || "x"}:${process.arch || "x"}` : "browser";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pentatonic-ai/ai-agent-sdk",
3
- "version": "0.10.25",
3
+ "version": "0.10.27",
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",
@@ -460,7 +460,7 @@ class StoreRequest(BaseModel):
460
460
 
461
461
  class StoreBatchRequest(BaseModel):
462
462
  records: list[dict[str, Any]] = Field(default_factory=list)
463
- arena: str | None = "general"
463
+ arena: str | None = None # no "general" default — fail closed if absent
464
464
  # v1's optional pre-computed embeddings — passed through but we
465
465
  # re-embed regardless. The shared-embed optimisation lives at the
466
466
  # SDK level now (PR #58 retry-with-jitter); compat trusts the
@@ -611,7 +611,14 @@ async def _extract(arena: str, clientId: str, userId: str | None,
611
611
  return r.json()["event_id"]
612
612
 
613
613
 
614
- def _arena_of(meta: dict[str, Any] | None, fallback: str = "general") -> str:
614
+ def _arena_of(meta: dict[str, Any] | None, fallback: str | None = None) -> str | None:
615
+ # No "general" fallback. An arena-less write used to silently land in a
616
+ # shared "general" bucket that belongs to no tenant — a cross-tenant
617
+ # hazard (and the one thing that could co-mingle entities across tenants,
618
+ # since entity ids embed the arena). Callers MUST supply an arena; the
619
+ # /store + /store-batch handlers reject when this returns None. The SDK
620
+ # (engineStore/composeArena) always stamps one, so only a raw direct
621
+ # caller can hit the rejection — which is the point (fail closed).
615
622
  if not meta:
616
623
  return fallback
617
624
  if isinstance(meta.get("arena"), str) and meta["arena"]:
@@ -690,6 +697,10 @@ async def store(req: StoreRequest):
690
697
  { id, content, layerId, engine }."""
691
698
  meta = req.metadata or {}
692
699
  arena = _arena_of(meta)
700
+ if not arena:
701
+ # Fail closed: no arena = no tenant. Mirrors /search. (Was a silent
702
+ # "general" bucket — a cross-tenant hazard.)
703
+ raise HTTPException(400, "arena required")
693
704
  clientId = meta.get("clientId") or arena.split(":")[0]
694
705
  userId = meta.get("user_id") or (arena.split(":", 1)[1] if ":" in arena else None)
695
706
  source_kind = _source_kind_of(meta)
@@ -778,7 +789,7 @@ async def store_batch(req: StoreBatchRequest):
778
789
  if not req.records:
779
790
  return {"status": "ok", "inserted": 0, "ids": [], "engine": {}}
780
791
 
781
- arena_default = req.arena or "general"
792
+ arena_default = req.arena # may be None; per-record arena can still supply it
782
793
  texts = [r["content"] for r in req.records]
783
794
  embeddings = await _embed_batch(texts)
784
795
  if len(embeddings) != len(texts):
@@ -809,6 +820,10 @@ async def store_batch(req: StoreBatchRequest):
809
820
  for r in req.records:
810
821
  meta = r.get("metadata") or {}
811
822
  arena = _arena_of(meta, fallback=arena_default)
823
+ if not arena:
824
+ # Fail closed: every record needs an arena (batch-level or its own).
825
+ # No "general" bucket — an arena-less write belongs to no tenant.
826
+ raise HTTPException(400, "arena required (batch-level or per-record)")
812
827
  clientId = meta.get("clientId") or arena.split(":")[0]
813
828
  userId = meta.get("user_id") or (arena.split(":", 1)[1] if ":" in arena else None)
814
829
  source_kind = _source_kind_of(meta)
@@ -2180,7 +2180,15 @@ def upsert_relationships(
2180
2180
  %s, %s, %s, %s, %s, %s, %s, %s, %s::disclosure_class,
2181
2181
  %s::jsonb, COALESCE(%s, NOW()), COALESCE(%s, NOW())
2182
2182
  )
2183
- ON CONFLICT (id) DO UPDATE SET
2183
+ -- Conflict on the DECLARED idempotency key (arena, from, to,
2184
+ -- type), NOT the content-hash `id`. Fusion-Drive entity merges
2185
+ -- repoint from/to onto the master but keep the pre-merge `id`
2186
+ -- (fusion_drive/merge.py), so `id` no longer equals
2187
+ -- _content_id(current tuple). Conflicting on `id` then MISSES
2188
+ -- the repointed row and slams into the UNIQUE(arena,from,to,
2189
+ -- type) constraint — the UniqueViolation that was failing live
2190
+ -- distillation + the SEE-490 backfill on merged-entity edges.
2191
+ ON CONFLICT (arena, from_entity_id, to_entity_id, relationship_type) DO UPDATE SET
2184
2192
  weight = relationships.weight + EXCLUDED.weight,
2185
2193
  provenance_event_ids = (
2186
2194
  SELECT ARRAY(SELECT DISTINCT UNNEST(relationships.provenance_event_ids || EXCLUDED.provenance_event_ids))