@maximem/synap-js-sdk 0.5.2 → 0.5.6
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/anticipation-cache-LRE3TLK4.cjs.map +1 -1
- package/dist/{chunk-DJPYUPSV.js → chunk-RP4YOEUN.js} +6 -2
- package/dist/chunk-RP4YOEUN.js.map +1 -0
- package/dist/chunk-T372OL3Q.cjs.map +1 -1
- package/dist/chunk-T6U5PVAD.cjs.map +1 -1
- package/dist/{chunk-WWTGJZA7.cjs → chunk-WE4QSR6A.cjs} +6 -2
- package/dist/chunk-WE4QSR6A.cjs.map +1 -0
- package/dist/chunk-ZKOJDUN4.cjs.map +1 -1
- package/dist/env-55ULHNJZ.cjs.map +1 -1
- package/dist/grpc/index.cjs +3 -3
- package/dist/grpc/index.cjs.map +1 -1
- package/dist/grpc/index.d.cts +2 -2
- package/dist/grpc/index.d.ts +2 -2
- package/dist/grpc/index.js +2 -2
- package/dist/index.cjs +131 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +124 -7
- package/dist/index.d.ts +124 -7
- package/dist/index.js +107 -13
- package/dist/index.js.map +1 -1
- package/dist/stream-client-A6IO6E47.cjs +16 -0
- package/dist/stream-client-A6IO6E47.cjs.map +1 -0
- package/dist/{stream-client-5CGJNTH4.js → stream-client-Y5NFAMIH.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-DJPYUPSV.js.map +0 -1
- package/dist/chunk-WWTGJZA7.cjs.map +0 -1
- package/dist/stream-client-GJM4YSXW.cjs +0 -16
- package/dist/stream-client-GJM4YSXW.cjs.map +0 -1
- /package/dist/{stream-client-5CGJNTH4.js.map → stream-client-Y5NFAMIH.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/scaler/maximem_synap/synap/sdk/js/dist/chunk-T6U5PVAD.cjs","../src/behavior/anticipation.json","../src/behavior/contract.ts","../src/cache/bm25.ts","../src/context/anticipation-cache.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACF,wDAA6B;AAC7B;AACA;ACLA,IAAA,qBAAA,EAAA;AAAA,EACE,QAAA,EAAY,yQAAA;AAAA,EACZ,OAAA,EAAW,CAAA;AAAA,EACX,aAAA,EAAiB;AAAA,IACf,IAAA,EAAQ,geAAA;AAAA,IACR,QAAA,EAAY,+BAAA;AAAA,IACZ,OAAA,EAAW,KAAA;AAAA,IACX,QAAA,EAAY;AAAA,MACV;AAAA,QACE,OAAA,EAAW,iBAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,OAAA,EAAW,eAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,OAAA,EAAW,6BAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,OAAA,EAAW,8CAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,OAAA,EAAW,qDAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,OAAA,EAAW,uBAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,OAAA,EAAW,8BAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,OAAA,EAAW,uBAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,OAAA,EAAW,mBAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,OAAA,EAAW,qEAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,OAAA,EAAW,8CAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,OAAA,EAAW,qDAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,OAAA,EAAW,8DAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,OAAA,EAAW,gEAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,OAAA,EAAW,8DAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,OAAA,EAAW,8DAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,OAAA,EAAW,2BAAA;AAAA,QACX,KAAA,EAAS,GAAA;AAAA,QACT,IAAA,EAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF,CAAA;AAAA,EACA,UAAA,EAAc;AAAA,IACZ,IAAA,EAAQ;AAAA,MACN,KAAA,EAAS,GAAA;AAAA,MACT,IAAA,EAAQ;AAAA,IACV,CAAA;AAAA,IACA,UAAA,EAAc;AAAA,MACZ,KAAA,EAAS,IAAA;AAAA,MACT,IAAA,EAAQ;AAAA,IACV,CAAA;AAAA,IACA,yBAAA,EAA6B;AAAA,MAC3B,KAAA,EAAS,GAAA;AAAA,MACT,IAAA,EAAQ;AAAA,IACV,CAAA;AAAA,IACA,eAAA,EAAmB;AAAA,MACjB,KAAA,EAAS,GAAA;AAAA,MACT,IAAA,EAAQ;AAAA,IACV,CAAA;AAAA,IACA,iBAAA,EAAqB;AAAA,MACnB,KAAA,EAAS,GAAA;AAAA,MACT,IAAA,EAAQ;AAAA,IACV;AAAA,EACF,CAAA;AAAA,EACA,IAAA,EAAQ;AAAA,IACN,EAAA,EAAM,GAAA;AAAA,IACN,CAAA,EAAK,IAAA;AAAA,IACL,SAAA,EAAa,GAAA;AAAA,IACb,aAAA,EAAiB,CAAA;AAAA,IACjB,YAAA,EAAgB,CAAA;AAAA,IAChB,QAAA,EAAY;AAAA,MACV,OAAA;AAAA,MACA,OAAA;AAAA,MACA,OAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,KAAA;AAAA,MACA,KAAA;AAAA,MACA,KAAA;AAAA,MACA,KAAA;AAAA,MACA,KAAA;AAAA,MACA,KAAA;AAAA,MACA,IAAA;AAAA,MACA,IAAA;AAAA,MACA,IAAA;AAAA,MACA;AAAA,IACF,CAAA;AAAA,IACA,UAAA,EAAc;AAAA,MACZ,GAAA;AAAA,MACA,OAAA;AAAA,MACA,OAAA;AAAA,MACA,IAAA;AAAA,MACA,KAAA;AAAA,MACA,KAAA;AAAA,MACA,IAAA;AAAA,MACA,IAAA;AAAA,MACA,MAAA;AAAA,MACA,QAAA;AAAA,MACA,OAAA;AAAA,MACA,KAAA;AAAA,MACA,IAAA;AAAA,MACA,KAAA;AAAA,MACA,OAAA;AAAA,MACA,KAAA;AAAA,MACA,IAAA;AAAA,MACA,MAAA;AAAA,MACA,KAAA;AAAA,MACA,MAAA;AAAA,MACA,KAAA;AAAA,MACA,KAAA;AAAA,MACA,MAAA;AAAA,MACA,KAAA;AAAA,MACA,IAAA;AAAA,MACA,IAAA;AAAA,MACA,MAAA;AAAA,MACA,IAAA;AAAA,MACA,IAAA;AAAA,MACA,KAAA;AAAA,MACA,KAAA;AAAA,MACA,OAAA;AAAA,MACA,IAAA;AAAA,MACA,KAAA;AAAA,MACA,KAAA;AAAA,MACA,IAAA;AAAA,MACA,IAAA;AAAA,MACA,IAAA;AAAA,MACA,MAAA;AAAA,MACA,QAAA;AAAA,MACA,IAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,KAAA;AAAA,MACA,MAAA;AAAA,MACA,OAAA;AAAA,MACA,MAAA;AAAA,MACA,OAAA;AAAA,MACA,IAAA;AAAA,MACA,KAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,OAAA;AAAA,MACA,OAAA;AAAA,MACA,KAAA;AAAA,MACA,MAAA;AAAA,MACA,KAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA;AAAA,IACF,CAAA;AAAA,IACA,WAAA,EAAe;AAAA,MACb,OAAA,EAAW,kDAAA;AAAA,MACX,IAAA,EAAQ;AAAA,IACV,CAAA;AAAA,IACA,WAAA,EAAe;AAAA,EACjB,CAAA;AAAA,EACA,SAAA,EAAa;AAAA,IACX,QAAA,EAAY,2MAAA;AAAA,IACZ,6BAAA,EAAiC;AAAA,MAC/B,OAAA,EAAW,KAAA;AAAA,MACX,MAAA,EAAU;AAAA,QACR,MAAA;AAAA,QACA,GAAA;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAA;AAAA,IACA,8BAAA,EAAkC;AAAA,MAChC,OAAA,EAAW,KAAA;AAAA,MACX,MAAA,EAAU;AAAA,QACR,MAAA;AAAA,QACA,GAAA;AAAA,QACA;AAAA,MACF,CAAA;AAAA,MACA,IAAA,EAAQ;AAAA,IACV,CAAA;AAAA,IACA,mCAAA,EAAuC;AAAA,MACrC,OAAA,EAAW,KAAA;AAAA,MACX,MAAA,EAAU;AAAA,QACR,MAAA;AAAA,QACA,GAAA;AAAA,QACA;AAAA,MACF,CAAA;AAAA,MACA,IAAA,EAAQ;AAAA,IACV,CAAA;AAAA,IACA,6BAAA,EAAiC;AAAA,MAC/B,OAAA,EAAW,IAAA;AAAA,MACX,IAAA,EAAQ;AAAA,IACV,CAAA;AAAA,IACA,4BAAA,EAAgC;AAAA,MAC9B,OAAA,EAAW;AAAA,IACb;AAAA,EACF,CAAA;AAAA,EACA,aAAA,EAAiB;AAAA,IACf,IAAA,EAAQ,qdAAA;AAAA,IACR,QAAA,EAAY,8BAAA;AAAA,IACZ,OAAA,EAAW,IAAA;AAAA,IACX,oBAAA,EAAwB;AAAA,EAC1B,CAAA;AAAA,EACA,SAAA,EAAa;AAAA,IACX,kBAAA,EAAsB;AAAA,MACpB,KAAA,EAAS,EAAA;AAAA,MACT,IAAA,EAAQ;AAAA,IACV,CAAA;AAAA,IACA,qBAAA,EAAyB;AAAA,EAC3B,CAAA;AAAA,EACA,QAAA,EAAY;AAAA,IACV,QAAA,EAAY,ofAAA;AAAA,IACZ,IAAA,EAAQ,kWAAA;AAAA,IACR,SAAA,EAAa,GAAA;AAAA,IACb,MAAA,EAAU,GAAA;AAAA,IACV,IAAA,EAAQ,gDAAA;AAAA,IACR,OAAA,EAAW;AAAA,MACT,IAAA,EAAM,MAAA;AAAA,MACN,GAAA,EAAK,KAAA;AAAA,MACL,GAAA,EAAK;AAAA,IACP,CAAA;AAAA,IACA,YAAA,EAAgB,sIAAA;AAAA,IAChB,MAAA,EAAU,2ZAAA;AAAA,IACV,OAAA,EAAW;AAAA,MACT;AAAA,QACE,IAAA,EAAQ,CAAC,CAAA;AAAA,QACT,GAAA,EAAO,IAAA;AAAA,QACP,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,IAAA,EAAQ;AAAA,UACN,QAAA,EAAY;AAAA,QACd,CAAA;AAAA,QACA,GAAA,EAAO;AAAA,MACT,CAAA;AAAA,MACA;AAAA,QACE,IAAA,EAAQ;AAAA,UACN,QAAA,EAAY,MAAA;AAAA,UACZ,IAAA,EAAQ,UAAA;AAAA,UACR,IAAA,EAAQ;AAAA,QACV,CAAA;AAAA,QACA,GAAA,EAAO;AAAA,MACT,CAAA;AAAA,MACA;AAAA,QACE,IAAA,EAAQ;AAAA,UACN,IAAA,EAAQ,MAAA;AAAA,UACR,IAAA,EAAQ,UAAA;AAAA,UACR,QAAA,EAAY;AAAA,QACd,CAAA;AAAA,QACA,GAAA,EAAO,uCAAA;AAAA,QACP,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,IAAA,EAAQ;AAAA,UACN,QAAA,EAAY;AAAA,QACd,CAAA;AAAA,QACA,GAAA,EAAO;AAAA,MACT,CAAA;AAAA,MACA;AAAA,QACE,IAAA,EAAQ;AAAA,UACN,QAAA,EAAY;AAAA,QACd,CAAA;AAAA,QACA,GAAA,EAAO;AAAA,MACT,CAAA;AAAA,MACA;AAAA,QACE,IAAA,EAAQ;AAAA,UACN,QAAA,EAAY;AAAA,QACd,CAAA;AAAA,QACA,GAAA,EAAO;AAAA,MACT,CAAA;AAAA,MACA;AAAA,QACE,IAAA,EAAQ;AAAA,UACN,QAAA,EAAY;AAAA,QACd,CAAA;AAAA,QACA,GAAA,EAAO,kBAAA;AAAA,QACP,IAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA;AAAA,QACE,IAAA,EAAQ;AAAA,UACN,QAAA,EAAY;AAAA,QACd,CAAA;AAAA,QACA,GAAA,EAAO;AAAA,MACT,CAAA;AAAA,MACA;AAAA,QACE,IAAA,EAAQ;AAAA,UACN,QAAA,EAAY,GAAA;AAAA,UACZ,CAAA,EAAK;AAAA,QACP,CAAA;AAAA,QACA,GAAA,EAAO,gBAAA;AAAA,QACP,IAAA,EAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF,CAAA;ADOA;AACA;AEvSO,IAAM,SAAA,EAAW,oBAAA;AAGxB,IAAM,cAAA,kBAAgB,IAAI,GAAA,CAAI,CAAC,GAAA,EAAK,GAAA,EAAK,GAAG,CAAC,CAAA;AAE7C,SAAS,OAAA,CAAQ,OAAA,EAAiB,KAAA,EAAuB;AACvD,EAAA,IAAA,CAAA,MAAW,GAAA,GAAM,KAAA,EAAO;AACtB,IAAA,GAAA,CAAI,CAAC,aAAA,CAAc,GAAA,CAAI,EAAE,CAAA,EAAG;AAC1B,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,wBAAA,EAA2B,EAAE,CAAA,0EAAA;AAAA,MAE/B,CAAA;AAAA,IACF;AAAA,EACF;AACA,EAAA,OAAO,IAAI,MAAA,CAAO,OAAA,EAAS,KAAK,CAAA;AAClC;AAUO,IAAM,gBAAA,EAAqC,QAAA,CAAS,aAAA,CAAc,QAAA,CAAS,GAAA;AAAA,EAAI,CAAC,CAAA,EAAA,GACrF,OAAA,CAAQ,CAAA,CAAE,OAAA,mBAAS,CAAA,CAAE,KAAA,UAAS,IAAE;AAClC,CAAA;AAEO,IAAM,WAAA,EAAa;AAAA,EACxB,IAAA,EAAM,QAAA,CAAS,UAAA,CAAW,IAAA,CAAK,KAAA;AAAA,EAC/B,SAAA,EAAW,QAAA,CAAS,UAAA,CAAW,UAAA,CAAW,KAAA;AAAA,EAC1C,qBAAA,EAAuB,QAAA,CAAS,UAAA,CAAW,yBAAA,CAA0B,KAAA;AAAA;AAAA,EAErE,cAAA,EAAgB,QAAA,CAAS,UAAA,CAAW,eAAA,CAAgB,KAAA;AAAA;AAAA,EAEpD,eAAA,EAAiB,QAAA,CAAS,UAAA,CAAW,iBAAA,CAAkB;AACzD,CAAA;AAEO,IAAM,YAAA,EAAc,QAAA,CAAS,IAAA;AAC7B,IAAM,WAAA,EAAkC,IAAI,GAAA,CAAI,QAAA,CAAS,IAAA,CAAK,UAAU,CAAA;AAGxE,IAAM,SAAA,EAA8B,QAAA,CAAS,IAAA,CAAK,QAAA;AAClD,IAAM,mBAAA,EAAqB,QAAA,CAAS,IAAA,CAAK,WAAA,CAAY,OAAA;AAGrD,SAAS,aAAA,CAAc,WAAA,EAAiF;AAC7G,EAAA,IAAA,CAAA,MAAW,EAAA,oBAAK,WAAA,UAAe,CAAC,GAAA,EAAG;AACjC,IAAA,GAAA,CAAI,CAAC,CAAA,EAAG,QAAA;AACR,IAAA,IAAA,CAAA,MAAW,QAAA,GAAW,eAAA,EAAiB;AACrC,MAAA,GAAA,CAAI,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,IAAA;AAAA,IAC9B;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT;AFsRA;AACA;AGjYA,IAAM,aAAA,EAAe,WAAA,CAAY,YAAA;AACjC,IAAM,cAAA,EAAgB,WAAA,CAAY,aAAA;AAE3B,SAAS,IAAA,CAAK,IAAA,EAAsB;AAEzC,EAAA,IAAA,CAAA,MAAW,OAAA,GAAU,QAAA,EAAU;AAC7B,IAAA,GAAA,CAAI,IAAA,CAAK,QAAA,CAAS,MAAM,EAAA,GAAK,IAAA,CAAK,OAAA,EAAS,MAAA,CAAO,OAAA,GAAU,YAAA,EAAc;AACxE,MAAA,OAAO,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,IAAA,CAAK,OAAA,EAAS,MAAA,CAAO,MAAM,CAAA;AAAA,IAClD;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;AAWA,SAAS,YAAA,CAAA,EAAuB;AAI9B,EAAA,OAAO,IAAI,MAAA,CAAO,kBAAA,EAAoB,GAAG,CAAA;AAC3C;AAEA,IAAM,QAAA,EAAU,YAAA;AAGT,SAAS,QAAA,CAAS,IAAA,EAA2C;AAClE,EAAA,GAAA,CAAI,CAAC,IAAA,EAAM,OAAO,CAAC,CAAA;AAEnB,EAAA,MAAM,OAAA,EAAmB,CAAC,CAAA;AAC1B,EAAA,MAAM,UAAA,EAAY,IAAA,CAAK,OAAA,CAAQ,YAAA,CAAa,CAAA,EAAG,CAAC,MAAA,EAAQ,QAAA,EAAA,GAAqB;AAC3E,IAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,WAAA,CAAY,CAAC,CAAA;AAClC,IAAA,OAAO,GAAA;AAAA,EACT,CAAC,CAAA;AAED,EAAA,MAAM,MAAA,mBAAQ,SAAA,CAAU,WAAA,CAAY,CAAA,CAAE,KAAA,CAAM,OAAO,CAAA,UAAK,CAAC,GAAA;AACzD,EAAA,MAAM,IAAA,EAAM,MAAA,CAAO,KAAA,CAAM,CAAA;AACzB,EAAA,IAAA,CAAA,MAAW,EAAA,GAAK,KAAA,EAAO;AACrB,IAAA,GAAA,CAAI,CAAA,CAAE,OAAA,GAAU,cAAA,GAAiB,CAAC,UAAA,CAAW,GAAA,CAAI,CAAC,CAAA,EAAG,GAAA,CAAI,IAAA,CAAK,IAAA,CAAK,CAAC,CAAC,CAAA;AAAA,EACvE;AACA,EAAA,OAAO,GAAA;AACT;AAGO,IAAM,KAAA,YAAN,MAAM,MAAK;AAAA,EAChB,4BAAgB,UAAA,EAAY,WAAA,CAAY,UAAA;AAAA,EAE/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,WAAA,CACE,MAAA,EACA,GAAA,EAAa,WAAA,CAAY,EAAA,EACzB,EAAA,EAAY,WAAA,CAAY,CAAA,EACxB;AACA,IAAA,IAAA,CAAK,GAAA,EAAK,EAAA;AACV,IAAA,IAAA,CAAK,EAAA,EAAI,CAAA;AACT,IAAA,IAAA,CAAK,OAAA,EAAS,MAAA;AACd,IAAA,IAAA,CAAK,SAAA,EAAW,MAAA,CAAO,MAAA;AACvB,IAAA,IAAA,CAAK,QAAA,EAAU,MAAA,CAAO,GAAA,CAAI,CAAC,GAAA,EAAA,GAAQ,GAAA,CAAI,MAAM,CAAA;AAE7C,IAAA,IAAA,CAAK,MAAA,EAAQ,IAAA,CAAK,SAAA,EACd,IAAA,CAAK,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,EAAG,CAAA,EAAA,GAAM,EAAA,EAAI,CAAA,EAAG,CAAC,EAAA,EAAI,IAAA,CAAK,SAAA,EAC/C,CAAA;AAEJ,IAAA,MAAM,IAAA,kBAAM,IAAI,GAAA,CAAoB,CAAA;AACpC,IAAA,MAAM,SAAA,kBAAW,IAAI,GAAA,CAAY,CAAA;AACjC,IAAA,IAAA,CAAA,MAAW,IAAA,GAAO,MAAA,EAAQ,IAAA,CAAA,MAAW,EAAA,GAAK,GAAA,EAAK,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA;AAC7D,IAAA,IAAA,CAAA,MAAW,KAAA,GAAQ,QAAA,EAAU;AAC3B,MAAA,IAAI,GAAA,EAAK,CAAA;AACT,MAAA,IAAA,CAAA,MAAW,IAAA,GAAO,MAAA,EAAQ,GAAA,CAAI,GAAA,CAAI,QAAA,CAAS,IAAI,CAAA,EAAG,EAAA,EAAA;AAClD,MAAA,MAAM,IAAA,EAAM,IAAA,CAAK,GAAA,CAAA,CAAK,IAAA,CAAK,SAAA,EAAW,GAAA,EAAK,GAAA,EAAA,EAAA,CAAQ,GAAA,EAAK,GAAA,EAAA,EAAO,CAAG,CAAA;AAClE,MAAA,GAAA,CAAI,GAAA,CAAI,IAAA,EAAM,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,KAAA,CAAK,SAAS,CAAC,CAAA;AAAA,IAC7C;AACA,IAAA,IAAA,CAAK,IAAA,EAAM,GAAA;AAAA,EACb;AAAA,EAEA,KAAA,CAAM,WAAA,EAAgC,MAAA,EAAwB;AAC5D,IAAA,MAAM,IAAA,EAAM,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA;AAC9B,IAAA,GAAA,CAAI,IAAA,IAAQ,KAAA,CAAA,EAAW,OAAO,CAAA;AAC9B,IAAA,MAAM,GAAA,mBAAK,IAAA,CAAK,OAAA,CAAQ,MAAM,CAAA,UAAK,GAAA;AAEnC,IAAA,MAAM,MAAA,kBAAQ,IAAI,GAAA,CAAoB,CAAA;AACtC,IAAA,IAAA,CAAA,MAAW,EAAA,GAAK,GAAA,EAAK,KAAA,CAAM,GAAA,CAAI,CAAA,EAAA,kBAAI,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,UAAK,GAAA,EAAA,EAAK,CAAC,CAAA;AAEzD,IAAA,IAAI,EAAA,EAAI,CAAA;AACR,IAAA,IAAA,CAAA,MAAW,KAAA,GAAQ,WAAA,EAAa;AAC9B,MAAA,MAAM,GAAA,EAAK,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA;AACzB,MAAA,GAAA,CAAI,GAAA,IAAO,KAAA,CAAA,EAAW,QAAA;AACtB,MAAA,MAAM,IAAA,mBAAM,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,IAAI,CAAA,UAAK,KAAA,CAAK,WAAA;AACvC,MAAA,MAAM,UAAA,EAAY,GAAA,EAAA,CAAM,IAAA,CAAK,GAAA,EAAK,CAAA,CAAA;AAClC,MAAA,MAAM,YAAA,EAAc,GAAA,EAAK,IAAA,CAAK,GAAA,EAAA,CAAM,EAAA,EAAI,IAAA,CAAK,EAAA,EAAK,IAAA,CAAK,EAAA,EAAI,GAAA,EAAM,IAAA,CAAK,KAAA,CAAA;AACtE,MAAA,EAAA,GAAM,IAAA,EAAM,UAAA,EAAa,WAAA;AAAA,IAC3B;AACA,IAAA,OAAO,CAAA;AAAA,EACT;AAAA,EAEA,MAAA,CAAO,WAAA,EAA0C;AAC/C,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,EAAA,GAAM,IAAA,CAAK,KAAA,CAAM,WAAA,EAAa,CAAC,CAAC,CAAA;AAAA,EAC7D;AACF,iCAAA;AH+VA;AACA;AI7bA,IAAM,gBAAA,EAAkB,QAAA,CAAS,UAAA,CAAW,eAAA,CAAgB,KAAA;AAC5D,IAAM,kBAAA,EAAoB,QAAA,CAAS,UAAA,CAAW,iBAAA,CAAkB,KAAA;AAChE,IAAM,aAAA,EAAe,QAAA,CAAS,SAAA,CAAU,kBAAA,CAAmB,KAAA;AAC3D,IAAM,cAAA,EAAgB,QAAA,CAAS,SAAA,CAAU,qBAAA;AAGlC,IAAM,UAAA,EAAY,MAAA;AAezB,IAAM,eAAA,EAAiB,GAAA;AACvB,IAAM,YAAA,EAAc,GAAA;AAWb,SAAS,OAAA,CACd,SAAA,EACe;AACf,EAAA,GAAA,CAAI,CAAC,SAAA,EAAW,OAAO,IAAA;AACvB,EAAA,MAAM,MAAA,EAA4B,CAAC,CAAA;AACnC,EAAA,IAAA,CAAA,MAAW,CAAC,CAAA,EAAG,CAAC,EAAA,GAAK,MAAA,CAAO,OAAA,CAAQ,SAAS,CAAA,EAAG;AAC9C,IAAA,GAAA,CAAI,EAAA,IAAM,GAAA,GAAM,EAAA,IAAM,KAAA,GAAQ,EAAA,IAAM,KAAA,EAAA,GAAa,MAAA,CAAO,CAAC,EAAA,IAAM,EAAA,EAAI,QAAA;AACnE,IAAA,KAAA,CAAM,IAAA,CAAK,CAAC,MAAA,CAAO,CAAC,CAAA,EAAG,MAAA,CAAO,CAAC,CAAC,CAAC,CAAA;AAAA,EACnC;AACA,EAAA,GAAA,CAAI,KAAA,CAAM,OAAA,IAAW,CAAA,EAAG,OAAO,IAAA;AAC/B,EAAA,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,EAAA,GAAO,CAAA,CAAE,CAAC,EAAA,EAAI,CAAA,CAAE,CAAC,EAAA,EAAI,CAAA,EAAA,EAAK,CAAA,CAAE,CAAC,EAAA,EAAI,CAAA,CAAE,CAAC,EAAA,EAAI,EAAA,EAAI,CAAE,CAAA;AAC7D,EAAA,OAAO,KAAA,CACJ,GAAA,CAAI,CAAC,CAAC,CAAA,EAAG,CAAC,CAAA,EAAA,GAAM;AACf,IAAA,MAAM,QAAA,EAAU,CAAA,CACb,KAAA,CAAM,IAAI,CAAA,CAAE,IAAA,CAAK,MAAM,CAAA,CACvB,KAAA,CAAM,cAAc,CAAA,CAAE,IAAA,CAAK,KAAK,CAAA,CAChC,KAAA,CAAM,WAAW,CAAA,CAAE,IAAA,CAAK,KAAK,CAAA;AAChC,IAAA,OAAO,CAAA,EAAA;AAEH,EAAA;AACV;AAiBgB;AAIN,EAAA;AACV;AA8IM;AACA;AAEO;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEC,iBAAA;AACa,kBAAA;AACvB,kBAAA;AACoB,kBAAA;AACpB,kBAAA;AAER,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AAEI,EAAA;AACK,IAAA;AACT,EAAA;AAEI,EAAA;AACK,IAAA;AACT,EAAA;AAEM,EAAA;AACC,IAAA;AACA,IAAA;AAIC,IAAA;AAEF,IAAA;AAEE,IAAA;AACD,IAAA;AACH,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AAED,IAAA;AACE,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AACK,IAAA;AAGE,IAAA;AACD,MAAA;AACJ,MAAA;AACM,QAAA;AACN,MAAA;AACI,MAAA;AACC,MAAA;AACP,IAAA;AACF,EAAA;AAEO,EAAA;AACC,IAAA;AACJ,MAAA;AAAoB,MAAA;AAAiB,MAAA;AACrC,MAAA;AAAiB,MAAA;AAAuB,MAAA;AAAkB,MAAA;AACxD,IAAA;AAEC,IAAA;AAEC,IAAA;AACA,IAAA;AACJ,MAAA;AACA,MAAA;AAAU,MAAA;AAAY,MAAA;AAAU,MAAA;AAChC,MAAA;AACA,MAAA;AACA,MAAA;AAAsB,MAAA;AAAqB,MAAA;AAC3C,MAAA;AAAiB,MAAA;AAAmB,MAAA;AAC/B,MAAA;AAAO,MAAA;AACd,IAAA;AAEI,IAAA;AACF,MAAA;AACF,IAAA;AAEM,IAAA;AACD,IAAA;AACH,MAAA;AACF,IAAA;AAKI,IAAA;AACF,MAAA;AACF,IAAA;AAEM,IAAA;AACN,IAAA;AACI,IAAA;AACF,MAAA;AACF,IAAA;AAEM,IAAA;AACF,IAAA;AACJ,IAAA;AACM,IAAA;AACN,IAAA;AAKE,IAAA;AAGA,MAAA;AACF,IAAA;AAEI,IAAA;AACE,MAAA;AACC,MAAA;AACA,MAAA;AACP,IAAA;AAEM,IAAA;AACA,IAAA;AAEA,IAAA;AACJ,MAAA;AACK,MAAA;AACP,IAAA;AACA,IAAA;AAEM,IAAA;AACA,IAAA;AAEN,IAAA;AACQ,MAAA;AACA,MAAA;AACA,MAAA;AAEF,QAAA;AACA,QAAA;AACE,UAAA;AAAiB,UAAA;AAA0B,UAAA;AAC3C,UAAA;AACA,UAAA;AAA2B,UAAA;AAC7B,QAAA;AACD,MAAA;AACC,MAAA;AAA8B,QAAA;AAA2B,QAAA;AAAU,MAAA;AAClE,MAAA;AAAmC,QAAA;AAAiC,QAAA;AAAU,MAAA;AACnF,MAAA;AACF,IAAA;AAEA,IAAA;AACA,IAAA;AAEI,IAAA;AACF,MAAA;AACF,IAAA;AAEO,IAAA;AACD,IAAA;AAEA,IAAA;AACN,IAAA;AACI,IAAA;AACJ,IAAA;AACM,IAAA;AACN,IAAA;AAIM,IAAA;AACF,IAAA;AAGF,MAAA;AACF,IAAA;AAEM,IAAA;AACA,IAAA;AACN,IAAA;AACG,MAAA;AACD,MAAA;AACF,IAAA;AAIM,IAAA;AACF,IAAA;AACI,MAAA;AACN,MAAA;AACE,QAAA;AACI,QAAA;AACN,MAAA;AACF,IAAA;AAEA,IAAA;AACA,IAAA;AACA,IAAA;AACE,MAAA;AAA0B,MAAA;AAC1B,MAAA;AAAyD,MAAA;AACzD,IAAA;AACG,IAAA;AAEE,IAAA;AACL,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMA,EAAA;AACO,IAAA;AACE,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,EAAA;AACM,IAAA;AACJ,IAAA;AAEM,MAAA;AACA,MAAA;AAA+B,QAAA;AAAiC,QAAA;AAAW,MAAA;AACjF,IAAA;AACO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,EAAA;AACQ,IAAA;AACA,IAAA;AACA,IAAA;AACN,IAAA;AACQ,MAAA;AACF,MAAA;AACC,MAAA;AACP,IAAA;AAEM,IAAA;AACN,IAAA;AACQ,MAAA;AACA,MAAA;AACN,MAAA;AAIE,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACE,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACH,MAAA;AACI,MAAA;AACJ,MAAA;AACA,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACH,IAAA;AAEO,IAAA;AACL,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,EAAA;AAIO,IAAA;AACA,IAAA;AAED,IAAA;AACJ,IAAA;AACM,MAAA;AACC,MAAA;AAED,MAAA;AACA,MAAA;AACN,IAAA;AACI,IAAA;AAIC,IAAA;AACH,MAAA;AACF,IAAA;AACO,IAAA;AACT,EAAA;AAAA;AAGA,EAAA;AACQ,IAAA;AACN,IAAA;AACM,MAAA;AACH,MAAA;AACH,IAAA;AACO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,EAAA;AACO,IAAA;AACD,IAAA;AACJ,IAAA;AACM,MAAA;AAA2C,QAAA;AAAiC,QAAA;AAAW,MAAA;AAC7F,IAAA;AACO,IAAA;AACT,EAAA;AAEc,EAAA;AACP,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AAAA;AAIa,EAAA;AACX,IAAA;AACA,IAAA;AACK,IAAA;AACE,IAAA;AACT,EAAA;AAEa,EAAA;AAEP,IAAA;AACG,sBAAA;AACC,IAAA;AAER,IAAA;AACF,EAAA;AAEQ,EAAA;AASA,IAAA;AAIF,IAAA;AAEE,IAAA;AACA,IAAA;AACE,IAAA;AACR,IAAA;AACM,MAAA;AACA,MAAA;AACH,MAAA;AACD,MAAA;AACF,IAAA;AACI,IAAA;AAEJ,IAAA;AACA,IAAA;AACK,IAAA;AACE,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASQ,EAAA;AAGA,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACG,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASQ,EAAA;AAGA,IAAA;AACN,IAAA;AACO,MAAA;AACA,MAAA;AAED,MAAA;AACE,MAAA;AACR,IAAA;AACO,IAAA;AACT,EAAA;AAEQ,EAAA;AACD,IAAA;AACD,IAAA;AAEG,IAAA;AACT,EAAA;AAEQ,EAAA;AACA,IAAA;AAIA,IAAA;AACA,IAAA;AAEN,IAAA;AACQ,MAAA;AACA,MAAA;AACF,MAAA;AACN,IAAA;AACF,EAAA;AAEQ,EAAA;AACD,IAAA;AACA,IAAA;AAGA,IAAA;AACA,IAAA;AACP,EAAA;AACF;AAES;AAIA,EAAA;AAGT;AAES;AACA,EAAA;AACT;AJwKY;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/Users/scaler/maximem_synap/synap/sdk/js/dist/chunk-T6U5PVAD.cjs","sourcesContent":[null,"{\n \"$comment\": \"SOURCE OF TRUTH for anticipation-cache behavior, shared by the Python and JS/TS SDKs. Do not duplicate these values into either SDK's source. See ../README.md. Regexes are restricted to the intersection of Python 're' and ECMAScript; both SDKs have a parity test.\",\n \"version\": 1,\n \"recall_bypass\": {\n \"note\": \"WS4 of the playground query-hygiene plan. A recall-shaped question never serves from the anticipation cache. Asymmetric cost is why: a stale false-hit makes the agent answer 'I don't see that' while the store holds the fact (the BZQR6J cross-session failure), whereas bypassing costs only cold-fetch latency on a turn where the user already expects a lookup. Stem coverage cannot gate this \\u2014 false hits were measured at coverage=1.00. Ties break toward INCLUDING a pattern.\",\n \"env_flag\": \"SYNAP_SDK_CACHE_RECALL_BYPASS\",\n \"default\": false,\n \"patterns\": [\n {\n \"pattern\": \"\\\\bremind me\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4, playground query-hygiene plan).\"\n },\n {\n \"pattern\": \"\\\\bon file\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\b(your|my|the) records\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\bdo you (have|know|remember|recall|see)\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\bwhat (do|did) (i|you) (say|tell|mention|have)\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\bwhat('s| is) my\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\bwhere (am i|do i live)\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\bhow long have i\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\bwhich of my\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\bwhat .{0,40}\\\\b(again|earlier|last time|previously|yesterday)\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4). 'again' alone is deliberately NOT a marker: 'book me a ride again' is a task query, not a recall query.\"\n },\n {\n \"pattern\": \"\\\\bwill you use to (contact|reach|notify)\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\b(confirm|verify|double.?check)\\\\b.{0,40}\\\\bmy\\\\b\",\n \"flags\": \"i\",\n \"note\": \"2026-07-16 eval register issue #11: 'can you confirm my email address?' served a stale bundle. Also catches 'confirm my booking' \\u2014 an intentional over-match, costing one cloud fetch.\"\n },\n {\n \"pattern\": \"\\\\b(linked to|associated with|registered (to|on|with)) my\\\\b\",\n \"flags\": \"i\",\n \"note\": \"2026-07-16 eval register issue #11: 'what name is linked to my Uber account?' served a stale bundle.\"\n },\n {\n \"pattern\": \"\\\\b(best|preferred) way to (reach|contact|update|notify) me\\\\b\",\n \"flags\": \"i\",\n \"note\": \"2026-07-16 eval register issue #11: 'best way to contact me with updates' served a stale bundle.\"\n },\n {\n \"pattern\": \"\\\\bhow (do|will|can|should) you (reach|contact|notify) me\\\\b\",\n \"flags\": \"i\",\n \"note\": \"2026-07-16 eval register issue #11, sibling phrasing of the above.\"\n },\n {\n \"pattern\": \"\\\\bwhich .{0,40}\\\\b(are|will) you (going to )?(use|using)\\\\b\",\n \"flags\": \"i\",\n \"note\": \"2026-07-16 eval register issue #11: 'which email are you going to use?' served a stale bundle.\"\n },\n {\n \"pattern\": \"\\\\bon (my|the) account\\\\b\",\n \"flags\": \"i\",\n \"note\": \"2026-07-16 eval register issue #11.\"\n }\n ]\n },\n \"thresholds\": {\n \"bm25\": {\n \"value\": 1.5,\n \"note\": \"Nominal threshold. The value actually applied is derived per query: max(effective_floor, min(bm25, query_token_scale * len(query_tokens))). Short queries cannot reach the nominal score, so holding them to it would miss every time.\"\n },\n \"novel_term\": {\n \"value\": 0.45,\n \"note\": \"Fraction of query stems absent from the cached corpus that forces a miss.\"\n },\n \"min_corpus_for_novel_gate\": {\n \"value\": 200,\n \"note\": \"The novel-term gate is meaningless on a small corpus, where the ratio is dominated by ordinary English word forms. Measured: a fresh playground conversation reaches ~60 stems after the first push and would trip the 0.45 gate on essentially every second-turn query. 200 means the gate engages once a customer has ~3-4 typical bundles cached.\"\n },\n \"effective_floor\": {\n \"value\": 0.6,\n \"note\": \"Lower bound on the derived per-query threshold. Below this, BM25 stops discriminating.\"\n },\n \"query_token_scale\": {\n \"value\": 0.3,\n \"note\": \"Scales the threshold down for short queries: 0.3 points of score per query token.\"\n }\n },\n \"bm25\": {\n \"k1\": 1.5,\n \"b\": 0.75,\n \"idf_floor\": 0.5,\n \"min_token_len\": 2,\n \"min_stem_len\": 4,\n \"suffixes\": [\n \"ences\",\n \"ances\",\n \"ation\",\n \"tion\",\n \"sion\",\n \"ence\",\n \"ance\",\n \"ness\",\n \"ment\",\n \"able\",\n \"ible\",\n \"ical\",\n \"ally\",\n \"ies\",\n \"ive\",\n \"ful\",\n \"ous\",\n \"ing\",\n \"ary\",\n \"ly\",\n \"ed\",\n \"es\",\n \"s\"\n ],\n \"stop_words\": [\n \"a\",\n \"about\",\n \"after\",\n \"an\",\n \"and\",\n \"are\",\n \"at\",\n \"be\",\n \"been\",\n \"before\",\n \"being\",\n \"but\",\n \"by\",\n \"can\",\n \"could\",\n \"did\",\n \"do\",\n \"does\",\n \"for\",\n \"from\",\n \"had\",\n \"has\",\n \"have\",\n \"how\",\n \"if\",\n \"in\",\n \"into\",\n \"is\",\n \"it\",\n \"its\",\n \"may\",\n \"might\",\n \"no\",\n \"nor\",\n \"not\",\n \"of\",\n \"on\",\n \"or\",\n \"over\",\n \"should\",\n \"so\",\n \"than\",\n \"that\",\n \"the\",\n \"then\",\n \"these\",\n \"this\",\n \"those\",\n \"to\",\n \"was\",\n \"were\",\n \"what\",\n \"when\",\n \"where\",\n \"which\",\n \"who\",\n \"whom\",\n \"why\",\n \"will\",\n \"with\",\n \"would\"\n ],\n \"alias_token\": {\n \"pattern\": \"\\\\[\\\\[([A-Za-z][A-Za-z0-9_]*_[a-z0-9]{12})\\\\]\\\\]\",\n \"note\": \"An alias label as it appears in text: [[PERSON_PHONE_h2n7v5cx8m0d]]. Kept whole rather than split, because 'person' and 'phone' are boilerplate shared by every label of that field type and would make two different people's memories score alike. Mirrors synap/cloud/shared/tokenizer.py \\u2014 keep the two in step.\"\n },\n \"suffix_note\": \"Order matters: longest-first, because _stem returns on first match. Reordering this list silently changes every stem and therefore every BM25 score.\"\n },\n \"env_flags\": {\n \"$comment\": \"Read at CALL time, never at module load. Python does os.environ.get inside the function bodies; JS must do the same or late process.env mutation stops working and the flag looks broken. See gotcha G-S.\",\n \"SYNAP_SDK_CACHE_RECALL_BYPASS\": {\n \"default\": false,\n \"truthy\": [\n \"true\",\n \"1\",\n \"yes\"\n ]\n },\n \"SYNAP_SDK_CACHE_HONOR_TTL_HINT\": {\n \"default\": false,\n \"truthy\": [\n \"true\",\n \"1\",\n \"yes\"\n ],\n \"note\": \"A bundle's server ttl_hint_seconds may only SHORTEN the global TTL, never extend it.\"\n },\n \"SYNAP_SDK_CACHE_INVALIDATE_ON_WRITE\": {\n \"default\": false,\n \"truthy\": [\n \"true\",\n \"1\",\n \"yes\"\n ],\n \"note\": \"2026-07-16 eval register issue #8: the cloud invalidates its own Redis caches on ingest, but that stops at the process boundary.\"\n },\n \"SYNAP_SDK_CACHE_MAX_ENTRY_AGE\": {\n \"default\": null,\n \"note\": \"2026-07-16 eval register issue #6: stored_at is a sliding lease renewed on hit, so under steady traffic a pre-update snapshot never expired. Defaults to 2x the sliding TTL; 0 disables.\"\n },\n \"SYNAP_SDK_CACHE_COVERAGE_MIN\": {\n \"default\": null\n }\n },\n \"coverage_gate\": {\n \"note\": \"Fraction of the query's stems present in the picked items. A bundle can clear the BM25 threshold on one shared token ('account') while lacking the asked-for fact entirely: the false HIT behind the eval's 'forgot the name' failure. ALWAYS computed and reported; ENFORCED only when SYNAP_SDK_CACHE_COVERAGE_MIN is explicitly set, so default behavior is unchanged. When it does fire it must gate BEFORE any side effect, so a rejected lookup leaves cache state untouched.\",\n \"env_flag\": \"SYNAP_SDK_CACHE_COVERAGE_MIN\",\n \"default\": null,\n \"empty_query_coverage\": 1.0\n },\n \"telemetry\": {\n \"rejected_items_cap\": {\n \"value\": 20,\n \"note\": \"Rejected items reported per lookup, highest score first, so a large cache cannot bloat the payload.\"\n },\n \"content_preview_chars\": 120\n },\n \"rung_key\": {\n \"$comment\": \"SOURCE OF TRUTH for how a scope rung is written on the wire and compared in both SDK anticipation caches. The server stamps a pushed bundle with this string (ContextBundleProto.anticipation_scope_rung) and each SDK builds the same string from the `scope` path its own fetch names; a bundle is servable only when the two agree. Three implementations exist by necessity (server Python, SDK Python, SDK TypeScript) because the producer is not an SDK, so the vectors below are what stops them drifting.\",\n \"note\": \"Node ids are deliberately not what travels. They are registry surrogates and an SDK in a customer's process has never seen one; a caller names a rung with level keys and its own external ids, so that is the vocabulary the comparison happens in. Level keys are ^[a-z][a-z0-9_]{0,63}$ so they can hold neither separator and only the value needs escaping.\",\n \"separator\": \"|\",\n \"assign\": \"=\",\n \"sort\": \"by level key, ascending, plain codepoint order\",\n \"escapes\": {\n \"\\\\\": \"\\\\\\\\\",\n \"|\": \"\\\\p\",\n \"=\": \"\\\\e\"\n },\n \"escape_order\": \"backslash first. Reversing it makes `a|b` and `a\\\\pb` encode to the same string, which is a false match between two different rungs.\",\n \"absent\": \"null (an empty proto string on the wire) means the bundle or the request named no rung. Serving requires the two to be EQUAL, so absent matches only absent. That leaves every client without nested scoping byte-identical to today and refuses every genuinely cross-rung serve; the cost of the rule being wrong is a cold fetch, and the cost of a subset rule being wrong is one person's context handed to another.\",\n \"vectors\": [\n {\n \"path\": {},\n \"key\": null,\n \"note\": \"no path named\"\n },\n {\n \"path\": {\n \"customer\": \"acme\"\n },\n \"key\": \"customer=acme\"\n },\n {\n \"path\": {\n \"customer\": \"acme\",\n \"team\": \"payments\",\n \"user\": \"dana\"\n },\n \"key\": \"customer=acme|team=payments|user=dana\"\n },\n {\n \"path\": {\n \"user\": \"dana\",\n \"team\": \"payments\",\n \"customer\": \"acme\"\n },\n \"key\": \"customer=acme|team=payments|user=dana\",\n \"note\": \"insertion order must not change the key; a dict has whatever order the caller wrote and ladder order is not available here\"\n },\n {\n \"path\": {\n \"customer\": \"a|b\"\n },\n \"key\": \"customer=a\\\\pb\"\n },\n {\n \"path\": {\n \"customer\": \"a=b\"\n },\n \"key\": \"customer=a\\\\eb\"\n },\n {\n \"path\": {\n \"customer\": \"a\\\\b\"\n },\n \"key\": \"customer=a\\\\\\\\b\"\n },\n {\n \"path\": {\n \"customer\": \"a\\\\pb\"\n },\n \"key\": \"customer=a\\\\\\\\pb\",\n \"note\": \"the escape-order case: this must NOT collide with a|b above\"\n },\n {\n \"path\": {\n \"customer\": \"ab\"\n },\n \"key\": \"customer=ab\"\n },\n {\n \"path\": {\n \"customer\": \"a\",\n \"b\": \"c\"\n },\n \"key\": \"b=c|customer=a\",\n \"note\": \"sorted by key, so `customer=ab` and this cannot collapse\"\n }\n ]\n }\n}\n","/**\n * Loader for the shared SDK behavior contract.\n *\n * The values here are tuned against measured eval failures and are shared,\n * byte-identical, with the Python SDK. They live in data rather than code\n * specifically so the two SDKs cannot drift. See\n * `synap/sdk/CONTRACT/README.md`.\n *\n * `anticipation.json` is vendored into `src/` by\n * `synap/sdk/scripts/sync-behavior.sh` and imported (not read from disk) so\n * that the bundler inlines it. Reading it with `node:fs` at runtime would work\n * in Node and break in every bundled target: Edge, Workers, and anything under\n * webpack/turbopack, where `dist/` is rewritten and relative paths do not\n * survive. It is the same failure mode as `@grpc/proto-loader` reading a\n * `.proto` off disk (gotcha G-P).\n */\n\nimport contractJson from './anticipation.json' with { type: 'json' };\n\nexport interface RecallPattern {\n pattern: string;\n flags: string;\n note: string;\n}\n\nexport interface BehaviorContract {\n version: number;\n recall_bypass: {\n note: string;\n env_flag: string;\n default: boolean;\n patterns: RecallPattern[];\n };\n thresholds: {\n bm25: { value: number; note: string };\n novel_term: { value: number; note: string };\n min_corpus_for_novel_gate: { value: number; note: string };\n effective_floor: { value: number; note: string };\n query_token_scale: { value: number; note: string };\n };\n coverage_gate: {\n note: string;\n env_flag: string;\n default: number | null;\n empty_query_coverage: number;\n };\n telemetry: {\n rejected_items_cap: { value: number; note: string };\n content_preview_chars: number;\n };\n bm25: {\n k1: number;\n b: number;\n idf_floor: number;\n min_token_len: number;\n min_stem_len: number;\n suffixes: string[];\n stop_words: string[];\n alias_token: { pattern: string; note: string };\n };\n}\n\nexport const CONTRACT = contractJson as unknown as BehaviorContract;\n\n/** ECMAScript flags we accept. Anything else is invalid in Python too. */\nconst ALLOWED_FLAGS = new Set(['i', 'm', 's']);\n\nfunction compile(pattern: string, flags: string): RegExp {\n for (const ch of flags) {\n if (!ALLOWED_FLAGS.has(ch)) {\n throw new Error(\n `Unsupported regex flag '${ch}' in behavior contract. Flags must be ` +\n `valid in both Python and ECMAScript.`,\n );\n }\n }\n return new RegExp(pattern, flags);\n}\n\n/**\n * Compiled recall-bypass patterns, in contract order.\n *\n * Deliberately NOT using the `g` flag: a global RegExp carries `lastIndex`\n * between calls, so a shared instance would match on one query and then miss\n * on the next. That failure is intermittent and traffic-dependent, which is\n * the worst kind to debug.\n */\nexport const RECALL_PATTERNS: readonly RegExp[] = CONTRACT.recall_bypass.patterns.map((p) =>\n compile(p.pattern, p.flags ?? ''),\n);\n\nexport const THRESHOLDS = {\n bm25: CONTRACT.thresholds.bm25.value,\n novelTerm: CONTRACT.thresholds.novel_term.value,\n minCorpusForNovelGate: CONTRACT.thresholds.min_corpus_for_novel_gate.value,\n /** Lower bound on the derived per-query threshold. */\n effectiveFloor: CONTRACT.thresholds.effective_floor.value,\n /** Points of score allowed per query token when deriving the threshold. */\n queryTokenScale: CONTRACT.thresholds.query_token_scale.value,\n} as const;\n\nexport const BM25_PARAMS = CONTRACT.bm25;\nexport const STOP_WORDS: ReadonlySet<string> = new Set(CONTRACT.bm25.stop_words);\n// Order is load-bearing: stem() returns on first match, so this must stay\n// longest-first exactly as the contract lists it.\nexport const SUFFIXES: readonly string[] = CONTRACT.bm25.suffixes;\nexport const ALIAS_TOKEN_SOURCE = CONTRACT.bm25.alias_token.pattern;\n\n/** True when any query string looks like an explicit memory-recall ask. */\nexport function isRecallQuery(searchQuery: readonly (string | null | undefined)[] | null | undefined): boolean {\n for (const q of searchQuery ?? []) {\n if (!q) continue;\n for (const pattern of RECALL_PATTERNS) {\n if (pattern.test(q)) return true;\n }\n }\n return false;\n}\n","/**\n * BM25 scorer for the anticipation cache.\n *\n * A faithful port of `maximem_synap/cache/bm25.py`. Every tuned value comes\n * from the shared behavior contract, so the two implementations cannot drift\n * on parameters. What they *can* still drift on is arithmetic, which is what\n * the shared conformance corpus is for.\n */\n\nimport { BM25_PARAMS, STOP_WORDS, SUFFIXES, ALIAS_TOKEN_SOURCE } from '../behavior/contract.js';\n\nconst MIN_STEM_LEN = BM25_PARAMS.min_stem_len;\nconst MIN_TOKEN_LEN = BM25_PARAMS.min_token_len;\n\nexport function stem(word: string): string {\n // Order is load-bearing: longest suffix first, return on first match.\n for (const suffix of SUFFIXES) {\n if (word.endsWith(suffix) && word.length - suffix.length >= MIN_STEM_LEN) {\n return word.slice(0, word.length - suffix.length);\n }\n }\n return word;\n}\n\n/**\n * An alias label, as it appears in text: `[[PERSON_PHONE_h2n7v5cx8m0d]]`.\n *\n * Kept whole rather than split into `person`, `phone` and the suffix, because\n * the first two are boilerplate shared by every label of that field type and\n * would make two different people's memories score alike.\n *\n * Mirrors `synap/cloud/shared/tokenizer.py`. Keep the two in step.\n */\nfunction aliasTokenRe(): RegExp {\n // Constructed per call because `g` regexes carry `lastIndex`, and this one\n // is used with replace(). A module-level shared instance would produce\n // different results on alternating calls.\n return new RegExp(ALIAS_TOKEN_SOURCE, 'g');\n}\n\nconst WORD_RE = /[a-z0-9]+/g;\n\n/** Tokenize, lowercase, remove stop words, and stem. Alias labels survive whole. */\nexport function tokenize(text: string | null | undefined): string[] {\n if (!text) return [];\n\n const labels: string[] = [];\n const remainder = text.replace(aliasTokenRe(), (_match, captured: string) => {\n labels.push(captured.toLowerCase());\n return ' ';\n });\n\n const words = remainder.toLowerCase().match(WORD_RE) ?? [];\n const out = labels.slice();\n for (const w of words) {\n if (w.length >= MIN_TOKEN_LEN && !STOP_WORDS.has(w)) out.push(stem(w));\n }\n return out;\n}\n\n/** Okapi BM25 scorer over a small in-memory corpus. */\nexport class BM25 {\n static readonly IDF_FLOOR = BM25_PARAMS.idf_floor;\n\n readonly k1: number;\n readonly b: number;\n readonly corpus: readonly (readonly string[])[];\n readonly docCount: number;\n readonly avgdl: number;\n readonly docLens: readonly number[];\n readonly idf: ReadonlyMap<string, number>;\n\n constructor(\n corpus: readonly (readonly string[])[],\n k1: number = BM25_PARAMS.k1,\n b: number = BM25_PARAMS.b,\n ) {\n this.k1 = k1;\n this.b = b;\n this.corpus = corpus;\n this.docCount = corpus.length;\n this.docLens = corpus.map((doc) => doc.length);\n // Python falls back to 1.0 rather than dividing by zero on an empty corpus.\n this.avgdl = this.docCount\n ? this.docLens.reduce((a, n) => a + n, 0) / this.docCount\n : 1.0;\n\n const idf = new Map<string, number>();\n const allTerms = new Set<string>();\n for (const doc of corpus) for (const t of doc) allTerms.add(t);\n for (const term of allTerms) {\n let df = 0;\n for (const doc of corpus) if (doc.includes(term)) df++;\n const raw = Math.log((this.docCount - df + 0.5) / (df + 0.5) + 1.0);\n idf.set(term, Math.max(raw, BM25.IDF_FLOOR));\n }\n this.idf = idf;\n }\n\n score(queryTokens: readonly string[], docIdx: number): number {\n const doc = this.corpus[docIdx];\n if (doc === undefined) return 0;\n const dl = this.docLens[docIdx] ?? 0;\n\n const tfMap = new Map<string, number>();\n for (const t of doc) tfMap.set(t, (tfMap.get(t) ?? 0) + 1);\n\n let s = 0;\n for (const term of queryTokens) {\n const tf = tfMap.get(term);\n if (tf === undefined) continue;\n const idf = this.idf.get(term) ?? BM25.IDF_FLOOR;\n const numerator = tf * (this.k1 + 1);\n const denominator = tf + this.k1 * (1 - this.b + (this.b * dl) / this.avgdl);\n s += (idf * numerator) / denominator;\n }\n return s;\n }\n\n scores(queryTokens: readonly string[]): number[] {\n return this.corpus.map((_, i) => this.score(queryTokens, i));\n }\n}\n","/**\n * In-memory TTL cache for context bundles pushed over gRPC.\n *\n * Port of `maximem_synap/cache/anticipation_cache.py`.\n *\n * ## Why this is a faithful port and not a simplification\n *\n * `@maximem/synap-vercel-adk` already contains a native TS anticipation cache,\n * and it is a simplification: a hardcoded 30-minute TTL, a plain BM25 threshold\n * with no per-query derivation, and none of the gates below. Shipping that as\n * the SDK's cache would mean two retrieval behaviors under one brand, which is\n * gotcha G-C. Every gate here exists because a specific eval failed without it:\n *\n * - **Recall bypass** runs BEFORE scoring. The measured false hit scored 2.86\n * at coverage 1.00, so no score-side gate could ever have caught it.\n * - **The novel-term gate** is suppressed below a 200-stem corpus, because at\n * cold start ordinary English trips it on nearly every query.\n * - **The per-query threshold** scales down for short queries, which otherwise\n * cannot reach the nominal score at all.\n * - **The coverage gate** catches a bundle that clears BM25 on one shared\n * token (\"account\") while lacking the asked-for fact.\n *\n * All thresholds come from the shared behavior contract.\n */\n\nimport { BM25, tokenize } from '../cache/bm25.js';\nimport { CONTRACT, THRESHOLDS, isRecallQuery } from '../behavior/contract.js';\nimport { getEnvFlag, getEnvFloat } from '../util/env.js';\n\nconst EFFECTIVE_FLOOR = CONTRACT.thresholds.effective_floor.value;\nconst QUERY_TOKEN_SCALE = CONTRACT.thresholds.query_token_scale.value;\nconst REJECTED_CAP = CONTRACT.telemetry.rejected_items_cap.value;\nconst PREVIEW_CHARS = CONTRACT.telemetry.content_preview_chars;\n\n/** Sentinel scope that matches any requester. */\nexport const ANY_SCOPE = '_any';\n\n// ── the rung a request or a bundle stands at ───────────────────────────────\n//\n// A pushed bundle is stamped with the scope rung the server retrieved it at,\n// and a fetch names one through `scope_path`. Both are written the same way so\n// they can be compared as strings, because nothing in this process can resolve\n// anything: the server speaks in registry node ids and an SDK in a customer's\n// app has never seen one, so the comparison happens in the vocabulary the\n// CALLER uses, which is level keys and the client's own external ids.\n//\n// The recipe and its golden vectors live in the shared behavior contract\n// (CONTRACT/behavior/anticipation.json, `rung_key`). It is written three times\n// (here, the Python SDK, the server) because the producer is not an SDK, so\n// the usual \"port it once\" rule cannot apply. Do NOT re-tune it here.\nconst RUNG_SEPARATOR = '|';\nconst RUNG_ASSIGN = '=';\n\n/**\n * The rung a `scope_path` names, canonically, or null for no path.\n *\n * Sorted by level key because an object carries whatever order the caller\n * wrote it in, and the ladder's own order is not knowable here. Only the value\n * is escaped: level keys are `^[a-z][a-z0-9_]{0,63}$`, so they can hold\n * neither separator. Backslash is escaped first, or `a|b` and `a\\pb` would\n * encode to the same string, which is a false match between two rungs.\n */\nexport function rungKey(\n scopePath: Record<string, string> | null | undefined,\n): string | null {\n if (!scopePath) return null;\n const pairs: [string, string][] = [];\n for (const [k, v] of Object.entries(scopePath)) {\n if (k === '' || v === null || v === undefined || String(v) === '') continue;\n pairs.push([String(k), String(v)]);\n }\n if (pairs.length === 0) return null;\n pairs.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));\n return pairs\n .map(([k, v]) => {\n const escaped = v\n .split('\\\\').join('\\\\\\\\')\n .split(RUNG_SEPARATOR).join('\\\\p')\n .split(RUNG_ASSIGN).join('\\\\e');\n return `${k}${RUNG_ASSIGN}${escaped}`;\n })\n .join(RUNG_SEPARATOR);\n}\n\n/**\n * May a bundle stamped `bundleRung` answer a request at `requestRung`?\n *\n * Equality, and equality on purpose. A subset rule (\"the bundle names more\n * rungs than you did, close enough\") reads as the generous option and is the\n * dangerous one: the rung nobody named is exactly the rung that tells two\n * people sharing an external id apart, which is the case the whole ladder\n * exists for. Being wrong here costs a cold fetch; being wrong the other way\n * hands one person's context to another.\n *\n * null on either side means \"named no rung\". Both null is every request in\n * production today and it serves, so nothing changes for anyone who has not\n * turned nested scoping on. `''` normalises to null because a proto string\n * field has no null and an unstamped bundle arrives as the empty string.\n */\nexport function rungServes(\n bundleRung: string | null | undefined,\n requestRung: string | null | undefined,\n): boolean {\n return (bundleRung || null) === (requestRung || null);\n}\n\nexport interface BundleItem {\n item_id?: string;\n content?: string;\n [key: string]: unknown;\n}\n\nexport interface ContextBundle {\n bundleId: string;\n entityId?: string | null;\n conversationId?: string | null;\n itemsByType: Record<string, BundleItem[]>;\n /** Server hint; may only SHORTEN the global TTL, never extend it. */\n ttlHintSeconds?: number | null;\n /** Which anticipation pattern produced this bundle. Diagnostics only. */\n bundleType?: string | null;\n /** The queries the server anticipated. Diagnostics only. */\n searchQueries?: readonly string[] | null;\n /**\n * The scope rung the server retrieved this bundle at, as `rungKey` writes\n * one. Absent from anything an older server sent, which reads as \"named no\n * rung\" rather than as \"matches anything\".\n */\n scopeRung?: string | null;\n}\n\ninterface ItemRecord {\n bundleId: string;\n itemType: string;\n content: string;\n tokens: string[];\n itemDict: BundleItem;\n}\n\ninterface EntryRecord {\n bundleId: string;\n entityId: string;\n conversationId: string | null;\n bundleType: string | null;\n searchQueries: string[];\n /** Sliding lease, refreshed on hit unless TTL hints are honoured. */\n storedAt: number;\n /** Absolute creation time; the max-entry-age cap uses this, not the lease. */\n createdAt: number;\n ttlHintSeconds: number | null;\n scopeRung: string | null;\n}\n\nexport interface LookupResult {\n itemsByType: Record<string, BundleItem[]>;\n bundleIds: string[];\n score: number;\n coverage: number;\n}\n\nexport interface LookupParams {\n searchQuery?: readonly string[] | null;\n entityId?: string | null;\n customerId?: string | null;\n clientId?: string | null;\n conversationId?: string | null;\n /**\n * The rung this request names, from its `scope_path`. A SECOND, independent\n * dimension and NOT another tier of the entity funnel: the funnel widens and\n * the rung does not, because a rung is a boundary and a boundary that widens\n * is not one.\n */\n scopeRung?: string | null;\n maxItems?: number;\n}\n\nexport type ExitReason =\n | 'empty'\n | 'recall_bypass'\n | 'no_query_tokens'\n | 'novel_term_gate'\n | 'empty_corpus'\n | 'no_items_above_threshold'\n | 'coverage_gate'\n | 'hit';\n\nexport interface LookupTelemetry {\n searchQuery: string[];\n entityId: string | null;\n customerId: string | null;\n clientId: string | null;\n conversationId: string | null;\n scopeRung: string | null;\n scopeFilterAccepted: string[];\n novelTermRatio: number | null;\n bm25Threshold: number | null;\n bm25QueryTokens: string[];\n itemsPicked: unknown[];\n itemsRejected: unknown[];\n coverage: number | null;\n hit: boolean;\n exitReason: ExitReason;\n}\n\nexport interface SnapshotItemPreview {\n type: string;\n scope: string | null;\n content: string;\n}\n\nexport interface SnapshotItemRecord {\n bundle_id: string;\n item_type: string;\n tokens: string[];\n content: string;\n}\n\nexport interface SnapshotBundle {\n bundle_id: string;\n entity_id: string;\n conversation_id: string | null;\n bundle_type: string | null;\n search_queries: string[];\n scope_counts: Record<string, number>;\n total_items: number;\n items: SnapshotItemPreview[];\n}\n\nexport interface AnticipationCacheSnapshot {\n total_entries: number;\n total_item_records: number;\n scope_breakdown_overall: Record<string, number>;\n corpus_vocab_size: number;\n corpus_vocab_sample: string[];\n item_records: SnapshotItemRecord[];\n bundles: SnapshotBundle[];\n}\n\nexport interface AnticipationCacheOptions {\n ttlSeconds?: number;\n maxBundles?: number;\n /** Injected in tests. Defaults to a monotonic clock. */\n now?: () => number;\n onLookup?: (telemetry: LookupTelemetry) => void;\n}\n\nconst DEFAULT_TTL_SECONDS = 1800;\nconst DEFAULT_MAX_BUNDLES = 50;\n\nexport class AnticipationCache {\n private readonly ttlSeconds: number;\n private readonly maxBundles: number;\n private readonly now: () => number;\n private readonly onLookup: ((t: LookupTelemetry) => void) | undefined;\n\n private entries = new Map<string, EntryRecord>();\n private items: ItemRecord[] = [];\n private corpusVocab = new Set<string>();\n private bm25: BM25 | null = null;\n private bm25Dirty = true;\n\n constructor(options: AnticipationCacheOptions = {}) {\n this.ttlSeconds = options.ttlSeconds ?? DEFAULT_TTL_SECONDS;\n this.maxBundles = options.maxBundles ?? DEFAULT_MAX_BUNDLES;\n this.now = options.now ?? monotonicNow;\n this.onLookup = options.onLookup;\n }\n\n get size(): number {\n return this.entries.size;\n }\n\n get itemCount(): number {\n return this.items.length;\n }\n\n store(bundle: ContextBundle): void {\n if (!bundle.bundleId) return;\n this.evictExpired();\n\n // Client-scope knowledge (FAQs, policies) arrives with no entity, and the\n // sentinel makes it visible to every requester.\n const entityId = bundle.entityId || ANY_SCOPE;\n\n if (this.entries.has(bundle.bundleId)) this.dropBundle(bundle.bundleId);\n\n const now = this.now();\n this.entries.set(bundle.bundleId, {\n bundleId: bundle.bundleId,\n entityId,\n conversationId: bundle.conversationId ?? null,\n bundleType: bundle.bundleType ?? null,\n searchQueries: [...(bundle.searchQueries ?? [])],\n storedAt: now,\n createdAt: now,\n ttlHintSeconds: bundle.ttlHintSeconds ?? null,\n scopeRung: bundle.scopeRung || null,\n });\n\n for (const [itemType, list] of Object.entries(bundle.itemsByType ?? {})) {\n for (const item of list ?? []) {\n const content = String(item.content ?? '');\n const tokens = tokenize(content);\n this.items.push({ bundleId: bundle.bundleId, itemType, content, tokens, itemDict: item });\n for (const t of tokens) this.corpusVocab.add(t);\n }\n }\n this.bm25Dirty = true;\n\n // LRU by lease age.\n while (this.entries.size > this.maxBundles) {\n let oldest: EntryRecord | null = null;\n for (const e of this.entries.values()) {\n if (oldest === null || e.storedAt < oldest.storedAt) oldest = e;\n }\n if (oldest === null) break;\n this.dropBundle(oldest.bundleId);\n }\n }\n\n lookup(params: LookupParams): LookupResult | null {\n const {\n searchQuery = null, entityId = null, customerId = null,\n clientId = null, conversationId = null, scopeRung = null, maxItems = 10,\n } = params;\n\n this.evictExpired();\n\n const accepted = this.buildAcceptedScope(entityId, customerId, clientId);\n const telemetry: LookupTelemetry = {\n searchQuery: (searchQuery ?? []).filter((q): q is string => Boolean(q)),\n entityId, customerId, clientId, conversationId,\n scopeRung: scopeRung ?? null,\n scopeFilterAccepted: [...accepted].sort(),\n novelTermRatio: null, bm25Threshold: null, bm25QueryTokens: [],\n itemsPicked: [], itemsRejected: [], coverage: null,\n hit: false, exitReason: 'empty',\n };\n\n if (this.entries.size === 0 || this.items.length === 0) {\n return this.miss(telemetry, 'empty');\n }\n\n const hasQuery = (searchQuery ?? []).some((q) => q && q.trim() !== '');\n if (!hasQuery) {\n return this.freshnessLookup(entityId, scopeRung ?? null, maxItems, telemetry);\n }\n\n // Recall bypass fires BEFORE scoring: a stale false-hit on a recall-shaped\n // question makes the agent deny a fact the store holds, and the measured\n // failure cleared every score-side gate.\n if (getEnvFlag(CONTRACT.recall_bypass.env_flag) && isRecallQuery(searchQuery)) {\n return this.miss(telemetry, 'recall_bypass');\n }\n\n const queryTokens = (searchQuery ?? []).flatMap((q) => tokenize(q));\n telemetry.bm25QueryTokens = queryTokens;\n if (queryTokens.length === 0) {\n return this.miss(telemetry, 'no_query_tokens');\n }\n\n const uniqueStems = new Set(queryTokens);\n let novel = 0;\n for (const s of uniqueStems) if (!this.corpusVocab.has(s)) novel++;\n const novelRatio = uniqueStems.size ? novel / uniqueStems.size : 0;\n telemetry.novelTermRatio = round4(novelRatio);\n\n // Only meaningful once the corpus has real vocabulary. Below the floor the\n // ratio is dominated by ordinary English and would miss on nearly anything.\n if (\n this.corpusVocab.size >= THRESHOLDS.minCorpusForNovelGate &&\n novelRatio >= THRESHOLDS.novelTerm\n ) {\n return this.miss(telemetry, 'novel_term_gate');\n }\n\n if (this.bm25Dirty || this.bm25 === null) {\n if (this.items.length === 0) return this.miss(telemetry, 'empty_corpus');\n this.bm25 = new BM25(this.items.map((i) => i.tokens));\n this.bm25Dirty = false;\n }\n\n const scores = this.bm25.scores(queryTokens);\n const validBundles = this.getValidBundleIds(accepted, conversationId, scopeRung ?? null);\n\n const effectiveThreshold = Math.max(\n EFFECTIVE_FLOOR,\n Math.min(THRESHOLDS.bm25, QUERY_TOKEN_SCALE * queryTokens.length),\n );\n telemetry.bm25Threshold = round4(effectiveThreshold);\n\n const picked: { score: number; item: ItemRecord }[] = [];\n const rejected: { score: number; payload: Record<string, unknown> }[] = [];\n\n for (let idx = 0; idx < scores.length; idx++) {\n const item = this.items[idx]!;\n const score = scores[idx]!;\n const reject = (reason: string) =>\n rejected.push({\n score,\n payload: {\n item_index: idx, bundle_id: item.bundleId, item_type: item.itemType,\n content_first_120: item.content.slice(0, PREVIEW_CHARS),\n bm25_score: round4(score), reason,\n },\n });\n if (score < effectiveThreshold) { reject('below_threshold'); continue; }\n if (!validBundles.has(item.bundleId)) { reject('scope_filter_excluded'); continue; }\n picked.push({ score, item });\n }\n\n rejected.sort((a, b) => b.score - a.score);\n telemetry.itemsRejected = rejected.slice(0, REJECTED_CAP).map((r) => r.payload);\n\n if (picked.length === 0) {\n return this.miss(telemetry, 'no_items_above_threshold');\n }\n\n picked.sort((a, b) => b.score - a.score);\n const top = picked.slice(0, maxItems);\n\n const pickedStems = new Set<string>();\n for (const { item } of top) for (const t of item.tokens) pickedStems.add(t);\n let overlap = 0;\n for (const s of uniqueStems) if (pickedStems.has(s)) overlap++;\n const coverage = uniqueStems.size ? overlap / uniqueStems.size : 1.0;\n telemetry.coverage = round4(coverage);\n\n // Observe-only unless explicitly configured. A malformed setting stays\n // observe-only rather than failing closed on every lookup.\n const coverageMin = getEnvFloat('SYNAP_SDK_CACHE_COVERAGE_MIN');\n if (coverageMin !== null && coverage < coverageMin) {\n // Gate BEFORE any side effect, so a rejected lookup leaves cache state\n // untouched and does not refresh a lease.\n return this.miss(telemetry, 'coverage_gate');\n }\n\n const itemsByType: Record<string, BundleItem[]> = {};\n const bundleIds = new Set<string>();\n for (const { item } of top) {\n (itemsByType[item.itemType] ??= []).push(item.itemDict);\n bundleIds.add(item.bundleId);\n }\n\n // Honouring a TTL hint also means a hit stops renewing the lease, otherwise\n // a hinted bundle under steady traffic would outlive its hint.\n const refreshOnHit = !getEnvFlag('SYNAP_SDK_CACHE_HONOR_TTL_HINT');\n if (refreshOnHit) {\n const now = this.now();\n for (const id of bundleIds) {\n const entry = this.entries.get(id);\n if (entry) entry.storedAt = now;\n }\n }\n\n telemetry.hit = true;\n telemetry.exitReason = 'hit';\n telemetry.itemsPicked = top.map(({ score, item }) => ({\n bundle_id: item.bundleId, item_type: item.itemType,\n content_first_120: item.content.slice(0, PREVIEW_CHARS), bm25_score: round4(score),\n }));\n this.fire(telemetry);\n\n return {\n itemsByType,\n bundleIds: [...bundleIds],\n score: top[0]!.score,\n coverage,\n };\n }\n\n /**\n * Drop this entity's bundles after a write, so the cache stops serving a\n * pre-write view. Gated on the flag: this is the write-path hook.\n */\n invalidateEntity(entityId: string): number {\n if (!getEnvFlag('SYNAP_SDK_CACHE_INVALIDATE_ON_WRITE')) return 0;\n return this.dropEntity(entityId);\n }\n\n /**\n * Unconditionally drop this entity's bundles. Backs `cache.clear_user` and\n * `cache.clear_customer`, which are explicit user requests and so are not\n * gated on the invalidate-on-write flag.\n */\n dropEntity(entityId: string): number {\n let dropped = 0;\n for (const entry of [...this.entries.values()]) {\n // Never drop shared client-scope knowledge on one entity's behalf.\n if (entry.entityId === ANY_SCOPE) continue;\n if (entry.entityId === entityId) { this.dropBundle(entry.bundleId); dropped++; }\n }\n return dropped;\n }\n\n /**\n * A read-only diagnostic projection of the cache.\n *\n * Backs `client.anticipation_cache_snapshot()` and mirrors Python's shape\n * key for key, including snake_case keys: this output gets logged, diffed\n * and pasted into issues across both SDKs, so it is a wire format.\n *\n * Note the 140-character content previews. That is Python's literal, and it\n * is deliberately NOT the 120 of `telemetry.content_preview_chars` from the\n * contract, which caps lookup telemetry rather than this snapshot.\n */\n snapshot(): AnticipationCacheSnapshot {\n const SNAPSHOT_PREVIEW_CHARS = 140;\n const overallScope = new Map<string, number>();\n const itemsByBundle = new Map<string, ItemRecord[]>();\n for (const record of this.items) {\n const list = itemsByBundle.get(record.bundleId);\n if (list === undefined) itemsByBundle.set(record.bundleId, [record]);\n else list.push(record);\n }\n\n const bundles: SnapshotBundle[] = [];\n for (const [bundleId, entry] of this.entries) {\n const perBundle = new Map<string, number>();\n const itemPreviews: SnapshotItemPreview[] = [];\n for (const record of itemsByBundle.get(bundleId) ?? []) {\n // `scope` is absent on plenty of items; Python's Counter happily keys\n // on None, which JSON-serialises to null, so preserve the distinction\n // rather than coercing to a string.\n const scope = (record.itemDict['scope'] ?? null) as string | null;\n const key = scope === null ? '' : scope;\n overallScope.set(key, (overallScope.get(key) ?? 0) + 1);\n perBundle.set(key, (perBundle.get(key) ?? 0) + 1);\n itemPreviews.push({\n type: record.itemType,\n scope,\n content: record.content.slice(0, SNAPSHOT_PREVIEW_CHARS),\n });\n }\n let totalItems = 0;\n for (const n of perBundle.values()) totalItems += n;\n bundles.push({\n bundle_id: bundleId,\n entity_id: entry.entityId,\n conversation_id: entry.conversationId,\n bundle_type: entry.bundleType,\n search_queries: [...entry.searchQueries],\n scope_counts: Object.fromEntries(perBundle),\n total_items: totalItems,\n items: itemPreviews,\n });\n }\n\n return {\n total_entries: this.entries.size,\n total_item_records: this.items.length,\n scope_breakdown_overall: Object.fromEntries(overallScope),\n corpus_vocab_size: this.corpusVocab.size,\n corpus_vocab_sample: [...this.corpusVocab].sort().slice(0, 80),\n item_records: this.items.slice(0, 20).map((rec) => ({\n bundle_id: rec.bundleId,\n item_type: rec.itemType,\n tokens: rec.tokens.slice(0, 25),\n content: rec.content.slice(0, SNAPSHOT_PREVIEW_CHARS),\n })),\n bundles,\n };\n }\n\n /**\n * The freshest `user_summary` bundle for this entity, or null.\n *\n * `entityId` is REQUIRED, and a missing one refuses the lookup rather than\n * falling back to the freshest summary across all users. Python hardened this\n * for exactly that reason: the permissive version could splice user A's\n * summary into user B's response on the conversation-scope path.\n */\n lookupUserSummary(\n entityId: string | null | undefined,\n scopeRung: string | null = null,\n ): Record<string, unknown> | null {\n if (!entityId) return null;\n this.evictExpired();\n\n let freshest: EntryRecord | null = null;\n for (const entry of this.entries.values()) {\n if (entry.bundleType !== 'user_summary') continue;\n if (!rungServes(entry.scopeRung, scopeRung)) continue;\n // ANY_SCOPE covers client-shared summaries, which are safe for everyone.\n if (entry.entityId !== entityId && entry.entityId !== ANY_SCOPE) continue;\n if (freshest === null || entry.storedAt > freshest.storedAt) freshest = entry;\n }\n if (freshest === null) return null;\n\n // Serving refreshes the sliding lease, unless the server's TTL hint is\n // being honoured, in which case the hint owns expiry.\n if (!getEnvFlag('SYNAP_SDK_CACHE_HONOR_TTL_HINT')) {\n freshest.storedAt = this.now();\n }\n return this.#bundleFor(freshest.bundleId);\n }\n\n /** Rebuild a bundle's items_by_type from the flat item records. */\n #bundleFor(bundleId: string): Record<string, unknown> {\n const itemsByType: Record<string, BundleItem[]> = {};\n for (const record of this.items) {\n if (record.bundleId !== bundleId) continue;\n (itemsByType[record.itemType] ??= []).push(record.itemDict);\n }\n return { bundle_id: bundleId, items_by_type: itemsByType };\n }\n\n /**\n * Drop every bundle belonging to one conversation.\n *\n * Backs the `compaction_update` signal on the Listen stream: a compaction\n * rewrites the conversation's summary, so any bundle built from the previous\n * state is stale and must not keep serving for the rest of its TTL.\n *\n * Unlike `dropEntity`, client-scope bundles are NOT spared: a bundle is only\n * dropped here if it is bound to this specific conversation, and a shared\n * client-scope bundle has no conversation id to match.\n */\n dropConversation(conversationId: string): number {\n if (!conversationId) return 0;\n let dropped = 0;\n for (const entry of [...this.entries.values()]) {\n if (entry.conversationId === conversationId) { this.dropBundle(entry.bundleId); dropped++; }\n }\n return dropped;\n }\n\n clear(): void {\n this.entries.clear();\n this.items = [];\n this.corpusVocab.clear();\n this.bm25 = null;\n this.bm25Dirty = true;\n }\n\n // ── internals ───────────────────────────────────────────────────────────────\n\n private miss(telemetry: LookupTelemetry, reason: ExitReason): null {\n telemetry.exitReason = reason;\n telemetry.hit = false;\n this.fire(telemetry);\n return null;\n }\n\n private fire(telemetry: LookupTelemetry): void {\n // Exception-safe by contract: a buggy hook must never break the SDK.\n try {\n this.onLookup?.(telemetry);\n } catch {\n /* ignored on purpose */\n }\n }\n\n private freshnessLookup(\n entityId: string | null,\n scopeRung: string | null,\n maxItems: number,\n telemetry: LookupTelemetry,\n ): LookupResult | null {\n // This path serves a WHOLE bundle on no query at all, so it is the least\n // filtered way out of this cache and needs the rung as much as the scored\n // path does.\n const candidates = [...this.entries.values()]\n .filter((e) => rungServes(e.scopeRung, scopeRung))\n .filter((e) => entityId === null || e.entityId === entityId || e.entityId === ANY_SCOPE)\n .sort((a, b) => b.storedAt - a.storedAt);\n if (candidates.length === 0) return this.miss(telemetry, 'empty');\n\n const newest = candidates[0]!;\n const itemsByType: Record<string, BundleItem[]> = {};\n let n = 0;\n for (const item of this.items) {\n if (item.bundleId !== newest.bundleId) continue;\n if (n >= maxItems) break;\n (itemsByType[item.itemType] ??= []).push(item.itemDict);\n n++;\n }\n if (n === 0) return this.miss(telemetry, 'empty');\n\n telemetry.hit = true;\n telemetry.exitReason = 'hit';\n this.fire(telemetry);\n return { itemsByType, bundleIds: [newest.bundleId], score: 0, coverage: 1.0 };\n }\n\n /**\n * The widened scope-match set for a request.\n *\n * A narrower-scope request also matches broader-scope bundles for the same\n * customer or client. Falsy ids are dropped so an empty customer_id cannot\n * accidentally match bundles keyed at \"\".\n */\n private buildAcceptedScope(\n entityId: string | null, customerId: string | null, clientId: string | null,\n ): Set<string> {\n const accepted = new Set<string>([ANY_SCOPE]);\n if (entityId) accepted.add(entityId);\n if (customerId) accepted.add(customerId);\n if (clientId) accepted.add(clientId);\n return accepted;\n }\n\n /**\n * The rung is checked FIRST and it never widens. Everything else here widens\n * deliberately: a user-scope request reaches customer-shared bundles, and a\n * conversation-scope request reaches user-scope ones. A rung is the opposite\n * kind of thing, so it is checked before the tiers and no amount of widening\n * can put a cross-rung bundle back in.\n */\n private getValidBundleIds(\n accepted: Set<string>, conversationId: string | null, scopeRung: string | null,\n ): Set<string> {\n const valid = new Set<string>();\n for (const entry of this.entries.values()) {\n if (!rungServes(entry.scopeRung, scopeRung)) continue;\n if (!accepted.has(entry.entityId)) continue;\n // A conversation-scoped bundle is only valid inside its conversation.\n if (entry.conversationId && conversationId && entry.conversationId !== conversationId) continue;\n valid.add(entry.bundleId);\n }\n return valid;\n }\n\n private effectiveTtl(entry: EntryRecord): number {\n if (!getEnvFlag('SYNAP_SDK_CACHE_HONOR_TTL_HINT')) return this.ttlSeconds;\n if (entry.ttlHintSeconds == null || entry.ttlHintSeconds <= 0) return this.ttlSeconds;\n // A hint may only shorten.\n return Math.min(this.ttlSeconds, entry.ttlHintSeconds);\n }\n\n private evictExpired(): void {\n const now = this.now();\n // Default cap is twice the sliding TTL; 0 disables. Without it a bundle\n // under steady traffic renews forever and can serve a retired value\n // indefinitely.\n const configuredMaxAge = getEnvFloat('SYNAP_SDK_CACHE_MAX_ENTRY_AGE');\n const maxAge = configuredMaxAge ?? this.ttlSeconds * 2;\n\n for (const entry of [...this.entries.values()]) {\n const idleExpired = now - entry.storedAt > this.effectiveTtl(entry);\n const ageExpired = maxAge > 0 && now - entry.createdAt > maxAge;\n if (idleExpired || ageExpired) this.dropBundle(entry.bundleId);\n }\n }\n\n private dropBundle(bundleId: string): void {\n if (!this.entries.delete(bundleId)) return;\n this.items = this.items.filter((i) => i.bundleId !== bundleId);\n // Vocabulary is rebuilt rather than decremented: a stem can be shared by\n // several bundles, so per-bundle subtraction would drop stems still in use.\n this.corpusVocab = new Set(this.items.flatMap((i) => i.tokens));\n this.bm25Dirty = true;\n }\n}\n\nfunction monotonicNow(): number {\n // performance.now() is monotonic and unaffected by wall-clock adjustments,\n // matching Python's time.monotonic(). Date.now() would let an NTP step\n // expire or resurrect every entry at once.\n return typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now() / 1000\n : Date.now() / 1000;\n}\n\nfunction round4(n: number): number {\n return Number(n.toFixed(4));\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/private/tmp/claude-501/-Users-anish-maximemai-maximem-synap/364ae6d0-b8b7-4119-bfaf-7676354661c6/scratchpad/jsrel/synap/sdk/js/dist/chunk-T6U5PVAD.cjs","../src/behavior/anticipation.json","../src/behavior/contract.ts","../src/cache/bm25.ts","../src/context/anticipation-cache.ts"],"names":[],"mappings":"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","file":"/private/tmp/claude-501/-Users-anish-maximemai-maximem-synap/364ae6d0-b8b7-4119-bfaf-7676354661c6/scratchpad/jsrel/synap/sdk/js/dist/chunk-T6U5PVAD.cjs","sourcesContent":[null,"{\n \"$comment\": \"SOURCE OF TRUTH for anticipation-cache behavior, shared by the Python and JS/TS SDKs. Do not duplicate these values into either SDK's source. See ../README.md. Regexes are restricted to the intersection of Python 're' and ECMAScript; both SDKs have a parity test.\",\n \"version\": 1,\n \"recall_bypass\": {\n \"note\": \"WS4 of the playground query-hygiene plan. A recall-shaped question never serves from the anticipation cache. Asymmetric cost is why: a stale false-hit makes the agent answer 'I don't see that' while the store holds the fact (the BZQR6J cross-session failure), whereas bypassing costs only cold-fetch latency on a turn where the user already expects a lookup. Stem coverage cannot gate this \\u2014 false hits were measured at coverage=1.00. Ties break toward INCLUDING a pattern.\",\n \"env_flag\": \"SYNAP_SDK_CACHE_RECALL_BYPASS\",\n \"default\": false,\n \"patterns\": [\n {\n \"pattern\": \"\\\\bremind me\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4, playground query-hygiene plan).\"\n },\n {\n \"pattern\": \"\\\\bon file\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\b(your|my|the) records\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\bdo you (have|know|remember|recall|see)\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\bwhat (do|did) (i|you) (say|tell|mention|have)\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\bwhat('s| is) my\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\bwhere (am i|do i live)\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\bhow long have i\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\bwhich of my\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\bwhat .{0,40}\\\\b(again|earlier|last time|previously|yesterday)\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4). 'again' alone is deliberately NOT a marker: 'book me a ride again' is a task query, not a recall query.\"\n },\n {\n \"pattern\": \"\\\\bwill you use to (contact|reach|notify)\\\\b\",\n \"flags\": \"i\",\n \"note\": \"Original set (WS4).\"\n },\n {\n \"pattern\": \"\\\\b(confirm|verify|double.?check)\\\\b.{0,40}\\\\bmy\\\\b\",\n \"flags\": \"i\",\n \"note\": \"2026-07-16 eval register issue #11: 'can you confirm my email address?' served a stale bundle. Also catches 'confirm my booking' \\u2014 an intentional over-match, costing one cloud fetch.\"\n },\n {\n \"pattern\": \"\\\\b(linked to|associated with|registered (to|on|with)) my\\\\b\",\n \"flags\": \"i\",\n \"note\": \"2026-07-16 eval register issue #11: 'what name is linked to my Uber account?' served a stale bundle.\"\n },\n {\n \"pattern\": \"\\\\b(best|preferred) way to (reach|contact|update|notify) me\\\\b\",\n \"flags\": \"i\",\n \"note\": \"2026-07-16 eval register issue #11: 'best way to contact me with updates' served a stale bundle.\"\n },\n {\n \"pattern\": \"\\\\bhow (do|will|can|should) you (reach|contact|notify) me\\\\b\",\n \"flags\": \"i\",\n \"note\": \"2026-07-16 eval register issue #11, sibling phrasing of the above.\"\n },\n {\n \"pattern\": \"\\\\bwhich .{0,40}\\\\b(are|will) you (going to )?(use|using)\\\\b\",\n \"flags\": \"i\",\n \"note\": \"2026-07-16 eval register issue #11: 'which email are you going to use?' served a stale bundle.\"\n },\n {\n \"pattern\": \"\\\\bon (my|the) account\\\\b\",\n \"flags\": \"i\",\n \"note\": \"2026-07-16 eval register issue #11.\"\n }\n ]\n },\n \"thresholds\": {\n \"bm25\": {\n \"value\": 1.5,\n \"note\": \"Nominal threshold. The value actually applied is derived per query: max(effective_floor, min(bm25, query_token_scale * len(query_tokens))). Short queries cannot reach the nominal score, so holding them to it would miss every time.\"\n },\n \"novel_term\": {\n \"value\": 0.45,\n \"note\": \"Fraction of query stems absent from the cached corpus that forces a miss.\"\n },\n \"min_corpus_for_novel_gate\": {\n \"value\": 200,\n \"note\": \"The novel-term gate is meaningless on a small corpus, where the ratio is dominated by ordinary English word forms. Measured: a fresh playground conversation reaches ~60 stems after the first push and would trip the 0.45 gate on essentially every second-turn query. 200 means the gate engages once a customer has ~3-4 typical bundles cached.\"\n },\n \"effective_floor\": {\n \"value\": 0.6,\n \"note\": \"Lower bound on the derived per-query threshold. Below this, BM25 stops discriminating.\"\n },\n \"query_token_scale\": {\n \"value\": 0.3,\n \"note\": \"Scales the threshold down for short queries: 0.3 points of score per query token.\"\n }\n },\n \"bm25\": {\n \"k1\": 1.5,\n \"b\": 0.75,\n \"idf_floor\": 0.5,\n \"min_token_len\": 2,\n \"min_stem_len\": 4,\n \"suffixes\": [\n \"ences\",\n \"ances\",\n \"ation\",\n \"tion\",\n \"sion\",\n \"ence\",\n \"ance\",\n \"ness\",\n \"ment\",\n \"able\",\n \"ible\",\n \"ical\",\n \"ally\",\n \"ies\",\n \"ive\",\n \"ful\",\n \"ous\",\n \"ing\",\n \"ary\",\n \"ly\",\n \"ed\",\n \"es\",\n \"s\"\n ],\n \"stop_words\": [\n \"a\",\n \"about\",\n \"after\",\n \"an\",\n \"and\",\n \"are\",\n \"at\",\n \"be\",\n \"been\",\n \"before\",\n \"being\",\n \"but\",\n \"by\",\n \"can\",\n \"could\",\n \"did\",\n \"do\",\n \"does\",\n \"for\",\n \"from\",\n \"had\",\n \"has\",\n \"have\",\n \"how\",\n \"if\",\n \"in\",\n \"into\",\n \"is\",\n \"it\",\n \"its\",\n \"may\",\n \"might\",\n \"no\",\n \"nor\",\n \"not\",\n \"of\",\n \"on\",\n \"or\",\n \"over\",\n \"should\",\n \"so\",\n \"than\",\n \"that\",\n \"the\",\n \"then\",\n \"these\",\n \"this\",\n \"those\",\n \"to\",\n \"was\",\n \"were\",\n \"what\",\n \"when\",\n \"where\",\n \"which\",\n \"who\",\n \"whom\",\n \"why\",\n \"will\",\n \"with\",\n \"would\"\n ],\n \"alias_token\": {\n \"pattern\": \"\\\\[\\\\[([A-Za-z][A-Za-z0-9_]*_[a-z0-9]{12})\\\\]\\\\]\",\n \"note\": \"An alias label as it appears in text: [[PERSON_PHONE_h2n7v5cx8m0d]]. Kept whole rather than split, because 'person' and 'phone' are boilerplate shared by every label of that field type and would make two different people's memories score alike. Mirrors synap/cloud/shared/tokenizer.py \\u2014 keep the two in step.\"\n },\n \"suffix_note\": \"Order matters: longest-first, because _stem returns on first match. Reordering this list silently changes every stem and therefore every BM25 score.\"\n },\n \"env_flags\": {\n \"$comment\": \"Read at CALL time, never at module load. Python does os.environ.get inside the function bodies; JS must do the same or late process.env mutation stops working and the flag looks broken. See gotcha G-S.\",\n \"SYNAP_SDK_CACHE_RECALL_BYPASS\": {\n \"default\": false,\n \"truthy\": [\n \"true\",\n \"1\",\n \"yes\"\n ]\n },\n \"SYNAP_SDK_CACHE_HONOR_TTL_HINT\": {\n \"default\": false,\n \"truthy\": [\n \"true\",\n \"1\",\n \"yes\"\n ],\n \"note\": \"A bundle's server ttl_hint_seconds may only SHORTEN the global TTL, never extend it.\"\n },\n \"SYNAP_SDK_CACHE_INVALIDATE_ON_WRITE\": {\n \"default\": false,\n \"truthy\": [\n \"true\",\n \"1\",\n \"yes\"\n ],\n \"note\": \"2026-07-16 eval register issue #8: the cloud invalidates its own Redis caches on ingest, but that stops at the process boundary.\"\n },\n \"SYNAP_SDK_CACHE_MAX_ENTRY_AGE\": {\n \"default\": null,\n \"note\": \"2026-07-16 eval register issue #6: stored_at is a sliding lease renewed on hit, so under steady traffic a pre-update snapshot never expired. Defaults to 2x the sliding TTL; 0 disables.\"\n },\n \"SYNAP_SDK_CACHE_COVERAGE_MIN\": {\n \"default\": null\n }\n },\n \"coverage_gate\": {\n \"note\": \"Fraction of the query's stems present in the picked items. A bundle can clear the BM25 threshold on one shared token ('account') while lacking the asked-for fact entirely: the false HIT behind the eval's 'forgot the name' failure. ALWAYS computed and reported; ENFORCED only when SYNAP_SDK_CACHE_COVERAGE_MIN is explicitly set, so default behavior is unchanged. When it does fire it must gate BEFORE any side effect, so a rejected lookup leaves cache state untouched.\",\n \"env_flag\": \"SYNAP_SDK_CACHE_COVERAGE_MIN\",\n \"default\": null,\n \"empty_query_coverage\": 1.0\n },\n \"telemetry\": {\n \"rejected_items_cap\": {\n \"value\": 20,\n \"note\": \"Rejected items reported per lookup, highest score first, so a large cache cannot bloat the payload.\"\n },\n \"content_preview_chars\": 120\n },\n \"rung_key\": {\n \"$comment\": \"SOURCE OF TRUTH for how a scope rung is written on the wire and compared in both SDK anticipation caches. The server stamps a pushed bundle with this string (ContextBundleProto.anticipation_scope_rung) and each SDK builds the same string from the `scope` path its own fetch names; a bundle is servable only when the two agree. Three implementations exist by necessity (server Python, SDK Python, SDK TypeScript) because the producer is not an SDK, so the vectors below are what stops them drifting.\",\n \"note\": \"Node ids are deliberately not what travels. They are registry surrogates and an SDK in a customer's process has never seen one; a caller names a rung with level keys and its own external ids, so that is the vocabulary the comparison happens in. Level keys are ^[a-z][a-z0-9_]{0,63}$ so they can hold neither separator and only the value needs escaping.\",\n \"separator\": \"|\",\n \"assign\": \"=\",\n \"sort\": \"by level key, ascending, plain codepoint order\",\n \"escapes\": {\n \"\\\\\": \"\\\\\\\\\",\n \"|\": \"\\\\p\",\n \"=\": \"\\\\e\"\n },\n \"escape_order\": \"backslash first. Reversing it makes `a|b` and `a\\\\pb` encode to the same string, which is a false match between two different rungs.\",\n \"absent\": \"null (an empty proto string on the wire) means the bundle or the request named no rung. Serving requires the two to be EQUAL, so absent matches only absent. That leaves every client without nested scoping byte-identical to today and refuses every genuinely cross-rung serve; the cost of the rule being wrong is a cold fetch, and the cost of a subset rule being wrong is one person's context handed to another.\",\n \"vectors\": [\n {\n \"path\": {},\n \"key\": null,\n \"note\": \"no path named\"\n },\n {\n \"path\": {\n \"customer\": \"acme\"\n },\n \"key\": \"customer=acme\"\n },\n {\n \"path\": {\n \"customer\": \"acme\",\n \"team\": \"payments\",\n \"user\": \"dana\"\n },\n \"key\": \"customer=acme|team=payments|user=dana\"\n },\n {\n \"path\": {\n \"user\": \"dana\",\n \"team\": \"payments\",\n \"customer\": \"acme\"\n },\n \"key\": \"customer=acme|team=payments|user=dana\",\n \"note\": \"insertion order must not change the key; a dict has whatever order the caller wrote and ladder order is not available here\"\n },\n {\n \"path\": {\n \"customer\": \"a|b\"\n },\n \"key\": \"customer=a\\\\pb\"\n },\n {\n \"path\": {\n \"customer\": \"a=b\"\n },\n \"key\": \"customer=a\\\\eb\"\n },\n {\n \"path\": {\n \"customer\": \"a\\\\b\"\n },\n \"key\": \"customer=a\\\\\\\\b\"\n },\n {\n \"path\": {\n \"customer\": \"a\\\\pb\"\n },\n \"key\": \"customer=a\\\\\\\\pb\",\n \"note\": \"the escape-order case: this must NOT collide with a|b above\"\n },\n {\n \"path\": {\n \"customer\": \"ab\"\n },\n \"key\": \"customer=ab\"\n },\n {\n \"path\": {\n \"customer\": \"a\",\n \"b\": \"c\"\n },\n \"key\": \"b=c|customer=a\",\n \"note\": \"sorted by key, so `customer=ab` and this cannot collapse\"\n }\n ]\n }\n}\n","/**\n * Loader for the shared SDK behavior contract.\n *\n * The values here are tuned against measured eval failures and are shared,\n * byte-identical, with the Python SDK. They live in data rather than code\n * specifically so the two SDKs cannot drift. See\n * `synap/sdk/CONTRACT/README.md`.\n *\n * `anticipation.json` is vendored into `src/` by\n * `synap/sdk/scripts/sync-behavior.sh` and imported (not read from disk) so\n * that the bundler inlines it. Reading it with `node:fs` at runtime would work\n * in Node and break in every bundled target: Edge, Workers, and anything under\n * webpack/turbopack, where `dist/` is rewritten and relative paths do not\n * survive. It is the same failure mode as `@grpc/proto-loader` reading a\n * `.proto` off disk (gotcha G-P).\n */\n\nimport contractJson from './anticipation.json' with { type: 'json' };\n\nexport interface RecallPattern {\n pattern: string;\n flags: string;\n note: string;\n}\n\nexport interface BehaviorContract {\n version: number;\n recall_bypass: {\n note: string;\n env_flag: string;\n default: boolean;\n patterns: RecallPattern[];\n };\n thresholds: {\n bm25: { value: number; note: string };\n novel_term: { value: number; note: string };\n min_corpus_for_novel_gate: { value: number; note: string };\n effective_floor: { value: number; note: string };\n query_token_scale: { value: number; note: string };\n };\n coverage_gate: {\n note: string;\n env_flag: string;\n default: number | null;\n empty_query_coverage: number;\n };\n telemetry: {\n rejected_items_cap: { value: number; note: string };\n content_preview_chars: number;\n };\n bm25: {\n k1: number;\n b: number;\n idf_floor: number;\n min_token_len: number;\n min_stem_len: number;\n suffixes: string[];\n stop_words: string[];\n alias_token: { pattern: string; note: string };\n };\n}\n\nexport const CONTRACT = contractJson as unknown as BehaviorContract;\n\n/** ECMAScript flags we accept. Anything else is invalid in Python too. */\nconst ALLOWED_FLAGS = new Set(['i', 'm', 's']);\n\nfunction compile(pattern: string, flags: string): RegExp {\n for (const ch of flags) {\n if (!ALLOWED_FLAGS.has(ch)) {\n throw new Error(\n `Unsupported regex flag '${ch}' in behavior contract. Flags must be ` +\n `valid in both Python and ECMAScript.`,\n );\n }\n }\n return new RegExp(pattern, flags);\n}\n\n/**\n * Compiled recall-bypass patterns, in contract order.\n *\n * Deliberately NOT using the `g` flag: a global RegExp carries `lastIndex`\n * between calls, so a shared instance would match on one query and then miss\n * on the next. That failure is intermittent and traffic-dependent, which is\n * the worst kind to debug.\n */\nexport const RECALL_PATTERNS: readonly RegExp[] = CONTRACT.recall_bypass.patterns.map((p) =>\n compile(p.pattern, p.flags ?? ''),\n);\n\nexport const THRESHOLDS = {\n bm25: CONTRACT.thresholds.bm25.value,\n novelTerm: CONTRACT.thresholds.novel_term.value,\n minCorpusForNovelGate: CONTRACT.thresholds.min_corpus_for_novel_gate.value,\n /** Lower bound on the derived per-query threshold. */\n effectiveFloor: CONTRACT.thresholds.effective_floor.value,\n /** Points of score allowed per query token when deriving the threshold. */\n queryTokenScale: CONTRACT.thresholds.query_token_scale.value,\n} as const;\n\nexport const BM25_PARAMS = CONTRACT.bm25;\nexport const STOP_WORDS: ReadonlySet<string> = new Set(CONTRACT.bm25.stop_words);\n// Order is load-bearing: stem() returns on first match, so this must stay\n// longest-first exactly as the contract lists it.\nexport const SUFFIXES: readonly string[] = CONTRACT.bm25.suffixes;\nexport const ALIAS_TOKEN_SOURCE = CONTRACT.bm25.alias_token.pattern;\n\n/** True when any query string looks like an explicit memory-recall ask. */\nexport function isRecallQuery(searchQuery: readonly (string | null | undefined)[] | null | undefined): boolean {\n for (const q of searchQuery ?? []) {\n if (!q) continue;\n for (const pattern of RECALL_PATTERNS) {\n if (pattern.test(q)) return true;\n }\n }\n return false;\n}\n","/**\n * BM25 scorer for the anticipation cache.\n *\n * A faithful port of `maximem_synap/cache/bm25.py`. Every tuned value comes\n * from the shared behavior contract, so the two implementations cannot drift\n * on parameters. What they *can* still drift on is arithmetic, which is what\n * the shared conformance corpus is for.\n */\n\nimport { BM25_PARAMS, STOP_WORDS, SUFFIXES, ALIAS_TOKEN_SOURCE } from '../behavior/contract.js';\n\nconst MIN_STEM_LEN = BM25_PARAMS.min_stem_len;\nconst MIN_TOKEN_LEN = BM25_PARAMS.min_token_len;\n\nexport function stem(word: string): string {\n // Order is load-bearing: longest suffix first, return on first match.\n for (const suffix of SUFFIXES) {\n if (word.endsWith(suffix) && word.length - suffix.length >= MIN_STEM_LEN) {\n return word.slice(0, word.length - suffix.length);\n }\n }\n return word;\n}\n\n/**\n * An alias label, as it appears in text: `[[PERSON_PHONE_h2n7v5cx8m0d]]`.\n *\n * Kept whole rather than split into `person`, `phone` and the suffix, because\n * the first two are boilerplate shared by every label of that field type and\n * would make two different people's memories score alike.\n *\n * Mirrors `synap/cloud/shared/tokenizer.py`. Keep the two in step.\n */\nfunction aliasTokenRe(): RegExp {\n // Constructed per call because `g` regexes carry `lastIndex`, and this one\n // is used with replace(). A module-level shared instance would produce\n // different results on alternating calls.\n return new RegExp(ALIAS_TOKEN_SOURCE, 'g');\n}\n\nconst WORD_RE = /[a-z0-9]+/g;\n\n/** Tokenize, lowercase, remove stop words, and stem. Alias labels survive whole. */\nexport function tokenize(text: string | null | undefined): string[] {\n if (!text) return [];\n\n const labels: string[] = [];\n const remainder = text.replace(aliasTokenRe(), (_match, captured: string) => {\n labels.push(captured.toLowerCase());\n return ' ';\n });\n\n const words = remainder.toLowerCase().match(WORD_RE) ?? [];\n const out = labels.slice();\n for (const w of words) {\n if (w.length >= MIN_TOKEN_LEN && !STOP_WORDS.has(w)) out.push(stem(w));\n }\n return out;\n}\n\n/** Okapi BM25 scorer over a small in-memory corpus. */\nexport class BM25 {\n static readonly IDF_FLOOR = BM25_PARAMS.idf_floor;\n\n readonly k1: number;\n readonly b: number;\n readonly corpus: readonly (readonly string[])[];\n readonly docCount: number;\n readonly avgdl: number;\n readonly docLens: readonly number[];\n readonly idf: ReadonlyMap<string, number>;\n\n constructor(\n corpus: readonly (readonly string[])[],\n k1: number = BM25_PARAMS.k1,\n b: number = BM25_PARAMS.b,\n ) {\n this.k1 = k1;\n this.b = b;\n this.corpus = corpus;\n this.docCount = corpus.length;\n this.docLens = corpus.map((doc) => doc.length);\n // Python falls back to 1.0 rather than dividing by zero on an empty corpus.\n this.avgdl = this.docCount\n ? this.docLens.reduce((a, n) => a + n, 0) / this.docCount\n : 1.0;\n\n const idf = new Map<string, number>();\n const allTerms = new Set<string>();\n for (const doc of corpus) for (const t of doc) allTerms.add(t);\n for (const term of allTerms) {\n let df = 0;\n for (const doc of corpus) if (doc.includes(term)) df++;\n const raw = Math.log((this.docCount - df + 0.5) / (df + 0.5) + 1.0);\n idf.set(term, Math.max(raw, BM25.IDF_FLOOR));\n }\n this.idf = idf;\n }\n\n score(queryTokens: readonly string[], docIdx: number): number {\n const doc = this.corpus[docIdx];\n if (doc === undefined) return 0;\n const dl = this.docLens[docIdx] ?? 0;\n\n const tfMap = new Map<string, number>();\n for (const t of doc) tfMap.set(t, (tfMap.get(t) ?? 0) + 1);\n\n let s = 0;\n for (const term of queryTokens) {\n const tf = tfMap.get(term);\n if (tf === undefined) continue;\n const idf = this.idf.get(term) ?? BM25.IDF_FLOOR;\n const numerator = tf * (this.k1 + 1);\n const denominator = tf + this.k1 * (1 - this.b + (this.b * dl) / this.avgdl);\n s += (idf * numerator) / denominator;\n }\n return s;\n }\n\n scores(queryTokens: readonly string[]): number[] {\n return this.corpus.map((_, i) => this.score(queryTokens, i));\n }\n}\n","/**\n * In-memory TTL cache for context bundles pushed over gRPC.\n *\n * Port of `maximem_synap/cache/anticipation_cache.py`.\n *\n * ## Why this is a faithful port and not a simplification\n *\n * `@maximem/synap-vercel-adk` already contains a native TS anticipation cache,\n * and it is a simplification: a hardcoded 30-minute TTL, a plain BM25 threshold\n * with no per-query derivation, and none of the gates below. Shipping that as\n * the SDK's cache would mean two retrieval behaviors under one brand, which is\n * gotcha G-C. Every gate here exists because a specific eval failed without it:\n *\n * - **Recall bypass** runs BEFORE scoring. The measured false hit scored 2.86\n * at coverage 1.00, so no score-side gate could ever have caught it.\n * - **The novel-term gate** is suppressed below a 200-stem corpus, because at\n * cold start ordinary English trips it on nearly every query.\n * - **The per-query threshold** scales down for short queries, which otherwise\n * cannot reach the nominal score at all.\n * - **The coverage gate** catches a bundle that clears BM25 on one shared\n * token (\"account\") while lacking the asked-for fact.\n *\n * All thresholds come from the shared behavior contract.\n */\n\nimport { BM25, tokenize } from '../cache/bm25.js';\nimport { CONTRACT, THRESHOLDS, isRecallQuery } from '../behavior/contract.js';\nimport { getEnvFlag, getEnvFloat } from '../util/env.js';\n\nconst EFFECTIVE_FLOOR = CONTRACT.thresholds.effective_floor.value;\nconst QUERY_TOKEN_SCALE = CONTRACT.thresholds.query_token_scale.value;\nconst REJECTED_CAP = CONTRACT.telemetry.rejected_items_cap.value;\nconst PREVIEW_CHARS = CONTRACT.telemetry.content_preview_chars;\n\n/** Sentinel scope that matches any requester. */\nexport const ANY_SCOPE = '_any';\n\n// ── the rung a request or a bundle stands at ───────────────────────────────\n//\n// A pushed bundle is stamped with the scope rung the server retrieved it at,\n// and a fetch names one through `scope_path`. Both are written the same way so\n// they can be compared as strings, because nothing in this process can resolve\n// anything: the server speaks in registry node ids and an SDK in a customer's\n// app has never seen one, so the comparison happens in the vocabulary the\n// CALLER uses, which is level keys and the client's own external ids.\n//\n// The recipe and its golden vectors live in the shared behavior contract\n// (CONTRACT/behavior/anticipation.json, `rung_key`). It is written three times\n// (here, the Python SDK, the server) because the producer is not an SDK, so\n// the usual \"port it once\" rule cannot apply. Do NOT re-tune it here.\nconst RUNG_SEPARATOR = '|';\nconst RUNG_ASSIGN = '=';\n\n/**\n * The rung a `scope_path` names, canonically, or null for no path.\n *\n * Sorted by level key because an object carries whatever order the caller\n * wrote it in, and the ladder's own order is not knowable here. Only the value\n * is escaped: level keys are `^[a-z][a-z0-9_]{0,63}$`, so they can hold\n * neither separator. Backslash is escaped first, or `a|b` and `a\\pb` would\n * encode to the same string, which is a false match between two rungs.\n */\nexport function rungKey(\n scopePath: Record<string, string> | null | undefined,\n): string | null {\n if (!scopePath) return null;\n const pairs: [string, string][] = [];\n for (const [k, v] of Object.entries(scopePath)) {\n if (k === '' || v === null || v === undefined || String(v) === '') continue;\n pairs.push([String(k), String(v)]);\n }\n if (pairs.length === 0) return null;\n pairs.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));\n return pairs\n .map(([k, v]) => {\n const escaped = v\n .split('\\\\').join('\\\\\\\\')\n .split(RUNG_SEPARATOR).join('\\\\p')\n .split(RUNG_ASSIGN).join('\\\\e');\n return `${k}${RUNG_ASSIGN}${escaped}`;\n })\n .join(RUNG_SEPARATOR);\n}\n\n/**\n * May a bundle stamped `bundleRung` answer a request at `requestRung`?\n *\n * Equality, and equality on purpose. A subset rule (\"the bundle names more\n * rungs than you did, close enough\") reads as the generous option and is the\n * dangerous one: the rung nobody named is exactly the rung that tells two\n * people sharing an external id apart, which is the case the whole ladder\n * exists for. Being wrong here costs a cold fetch; being wrong the other way\n * hands one person's context to another.\n *\n * null on either side means \"named no rung\". Both null is every request in\n * production today and it serves, so nothing changes for anyone who has not\n * turned nested scoping on. `''` normalises to null because a proto string\n * field has no null and an unstamped bundle arrives as the empty string.\n */\nexport function rungServes(\n bundleRung: string | null | undefined,\n requestRung: string | null | undefined,\n): boolean {\n return (bundleRung || null) === (requestRung || null);\n}\n\nexport interface BundleItem {\n item_id?: string;\n content?: string;\n [key: string]: unknown;\n}\n\nexport interface ContextBundle {\n bundleId: string;\n entityId?: string | null;\n conversationId?: string | null;\n itemsByType: Record<string, BundleItem[]>;\n /** Server hint; may only SHORTEN the global TTL, never extend it. */\n ttlHintSeconds?: number | null;\n /** Which anticipation pattern produced this bundle. Diagnostics only. */\n bundleType?: string | null;\n /** The queries the server anticipated. Diagnostics only. */\n searchQueries?: readonly string[] | null;\n /**\n * The scope rung the server retrieved this bundle at, as `rungKey` writes\n * one. Absent from anything an older server sent, which reads as \"named no\n * rung\" rather than as \"matches anything\".\n */\n scopeRung?: string | null;\n}\n\ninterface ItemRecord {\n bundleId: string;\n itemType: string;\n content: string;\n tokens: string[];\n itemDict: BundleItem;\n}\n\ninterface EntryRecord {\n bundleId: string;\n entityId: string;\n conversationId: string | null;\n bundleType: string | null;\n searchQueries: string[];\n /** Sliding lease, refreshed on hit unless TTL hints are honoured. */\n storedAt: number;\n /** Absolute creation time; the max-entry-age cap uses this, not the lease. */\n createdAt: number;\n ttlHintSeconds: number | null;\n scopeRung: string | null;\n}\n\nexport interface LookupResult {\n itemsByType: Record<string, BundleItem[]>;\n bundleIds: string[];\n score: number;\n coverage: number;\n}\n\nexport interface LookupParams {\n searchQuery?: readonly string[] | null;\n entityId?: string | null;\n customerId?: string | null;\n clientId?: string | null;\n conversationId?: string | null;\n /**\n * The rung this request names, from its `scope_path`. A SECOND, independent\n * dimension and NOT another tier of the entity funnel: the funnel widens and\n * the rung does not, because a rung is a boundary and a boundary that widens\n * is not one.\n */\n scopeRung?: string | null;\n maxItems?: number;\n}\n\nexport type ExitReason =\n | 'empty'\n | 'recall_bypass'\n | 'no_query_tokens'\n | 'novel_term_gate'\n | 'empty_corpus'\n | 'no_items_above_threshold'\n | 'coverage_gate'\n | 'hit';\n\nexport interface LookupTelemetry {\n searchQuery: string[];\n entityId: string | null;\n customerId: string | null;\n clientId: string | null;\n conversationId: string | null;\n scopeRung: string | null;\n scopeFilterAccepted: string[];\n novelTermRatio: number | null;\n bm25Threshold: number | null;\n bm25QueryTokens: string[];\n itemsPicked: unknown[];\n itemsRejected: unknown[];\n coverage: number | null;\n hit: boolean;\n exitReason: ExitReason;\n}\n\nexport interface SnapshotItemPreview {\n type: string;\n scope: string | null;\n content: string;\n}\n\nexport interface SnapshotItemRecord {\n bundle_id: string;\n item_type: string;\n tokens: string[];\n content: string;\n}\n\nexport interface SnapshotBundle {\n bundle_id: string;\n entity_id: string;\n conversation_id: string | null;\n bundle_type: string | null;\n search_queries: string[];\n scope_counts: Record<string, number>;\n total_items: number;\n items: SnapshotItemPreview[];\n}\n\nexport interface AnticipationCacheSnapshot {\n total_entries: number;\n total_item_records: number;\n scope_breakdown_overall: Record<string, number>;\n corpus_vocab_size: number;\n corpus_vocab_sample: string[];\n item_records: SnapshotItemRecord[];\n bundles: SnapshotBundle[];\n}\n\nexport interface AnticipationCacheOptions {\n ttlSeconds?: number;\n maxBundles?: number;\n /** Injected in tests. Defaults to a monotonic clock. */\n now?: () => number;\n onLookup?: (telemetry: LookupTelemetry) => void;\n}\n\nconst DEFAULT_TTL_SECONDS = 1800;\nconst DEFAULT_MAX_BUNDLES = 50;\n\nexport class AnticipationCache {\n private readonly ttlSeconds: number;\n private readonly maxBundles: number;\n private readonly now: () => number;\n private readonly onLookup: ((t: LookupTelemetry) => void) | undefined;\n\n private entries = new Map<string, EntryRecord>();\n private items: ItemRecord[] = [];\n private corpusVocab = new Set<string>();\n private bm25: BM25 | null = null;\n private bm25Dirty = true;\n\n constructor(options: AnticipationCacheOptions = {}) {\n this.ttlSeconds = options.ttlSeconds ?? DEFAULT_TTL_SECONDS;\n this.maxBundles = options.maxBundles ?? DEFAULT_MAX_BUNDLES;\n this.now = options.now ?? monotonicNow;\n this.onLookup = options.onLookup;\n }\n\n get size(): number {\n return this.entries.size;\n }\n\n get itemCount(): number {\n return this.items.length;\n }\n\n store(bundle: ContextBundle): void {\n if (!bundle.bundleId) return;\n this.evictExpired();\n\n // Client-scope knowledge (FAQs, policies) arrives with no entity, and the\n // sentinel makes it visible to every requester.\n const entityId = bundle.entityId || ANY_SCOPE;\n\n if (this.entries.has(bundle.bundleId)) this.dropBundle(bundle.bundleId);\n\n const now = this.now();\n this.entries.set(bundle.bundleId, {\n bundleId: bundle.bundleId,\n entityId,\n conversationId: bundle.conversationId ?? null,\n bundleType: bundle.bundleType ?? null,\n searchQueries: [...(bundle.searchQueries ?? [])],\n storedAt: now,\n createdAt: now,\n ttlHintSeconds: bundle.ttlHintSeconds ?? null,\n scopeRung: bundle.scopeRung || null,\n });\n\n for (const [itemType, list] of Object.entries(bundle.itemsByType ?? {})) {\n for (const item of list ?? []) {\n const content = String(item.content ?? '');\n const tokens = tokenize(content);\n this.items.push({ bundleId: bundle.bundleId, itemType, content, tokens, itemDict: item });\n for (const t of tokens) this.corpusVocab.add(t);\n }\n }\n this.bm25Dirty = true;\n\n // LRU by lease age.\n while (this.entries.size > this.maxBundles) {\n let oldest: EntryRecord | null = null;\n for (const e of this.entries.values()) {\n if (oldest === null || e.storedAt < oldest.storedAt) oldest = e;\n }\n if (oldest === null) break;\n this.dropBundle(oldest.bundleId);\n }\n }\n\n lookup(params: LookupParams): LookupResult | null {\n const {\n searchQuery = null, entityId = null, customerId = null,\n clientId = null, conversationId = null, scopeRung = null, maxItems = 10,\n } = params;\n\n this.evictExpired();\n\n const accepted = this.buildAcceptedScope(entityId, customerId, clientId);\n const telemetry: LookupTelemetry = {\n searchQuery: (searchQuery ?? []).filter((q): q is string => Boolean(q)),\n entityId, customerId, clientId, conversationId,\n scopeRung: scopeRung ?? null,\n scopeFilterAccepted: [...accepted].sort(),\n novelTermRatio: null, bm25Threshold: null, bm25QueryTokens: [],\n itemsPicked: [], itemsRejected: [], coverage: null,\n hit: false, exitReason: 'empty',\n };\n\n if (this.entries.size === 0 || this.items.length === 0) {\n return this.miss(telemetry, 'empty');\n }\n\n const hasQuery = (searchQuery ?? []).some((q) => q && q.trim() !== '');\n if (!hasQuery) {\n return this.freshnessLookup(entityId, scopeRung ?? null, maxItems, telemetry);\n }\n\n // Recall bypass fires BEFORE scoring: a stale false-hit on a recall-shaped\n // question makes the agent deny a fact the store holds, and the measured\n // failure cleared every score-side gate.\n if (getEnvFlag(CONTRACT.recall_bypass.env_flag) && isRecallQuery(searchQuery)) {\n return this.miss(telemetry, 'recall_bypass');\n }\n\n const queryTokens = (searchQuery ?? []).flatMap((q) => tokenize(q));\n telemetry.bm25QueryTokens = queryTokens;\n if (queryTokens.length === 0) {\n return this.miss(telemetry, 'no_query_tokens');\n }\n\n const uniqueStems = new Set(queryTokens);\n let novel = 0;\n for (const s of uniqueStems) if (!this.corpusVocab.has(s)) novel++;\n const novelRatio = uniqueStems.size ? novel / uniqueStems.size : 0;\n telemetry.novelTermRatio = round4(novelRatio);\n\n // Only meaningful once the corpus has real vocabulary. Below the floor the\n // ratio is dominated by ordinary English and would miss on nearly anything.\n if (\n this.corpusVocab.size >= THRESHOLDS.minCorpusForNovelGate &&\n novelRatio >= THRESHOLDS.novelTerm\n ) {\n return this.miss(telemetry, 'novel_term_gate');\n }\n\n if (this.bm25Dirty || this.bm25 === null) {\n if (this.items.length === 0) return this.miss(telemetry, 'empty_corpus');\n this.bm25 = new BM25(this.items.map((i) => i.tokens));\n this.bm25Dirty = false;\n }\n\n const scores = this.bm25.scores(queryTokens);\n const validBundles = this.getValidBundleIds(accepted, conversationId, scopeRung ?? null);\n\n const effectiveThreshold = Math.max(\n EFFECTIVE_FLOOR,\n Math.min(THRESHOLDS.bm25, QUERY_TOKEN_SCALE * queryTokens.length),\n );\n telemetry.bm25Threshold = round4(effectiveThreshold);\n\n const picked: { score: number; item: ItemRecord }[] = [];\n const rejected: { score: number; payload: Record<string, unknown> }[] = [];\n\n for (let idx = 0; idx < scores.length; idx++) {\n const item = this.items[idx]!;\n const score = scores[idx]!;\n const reject = (reason: string) =>\n rejected.push({\n score,\n payload: {\n item_index: idx, bundle_id: item.bundleId, item_type: item.itemType,\n content_first_120: item.content.slice(0, PREVIEW_CHARS),\n bm25_score: round4(score), reason,\n },\n });\n if (score < effectiveThreshold) { reject('below_threshold'); continue; }\n if (!validBundles.has(item.bundleId)) { reject('scope_filter_excluded'); continue; }\n picked.push({ score, item });\n }\n\n rejected.sort((a, b) => b.score - a.score);\n telemetry.itemsRejected = rejected.slice(0, REJECTED_CAP).map((r) => r.payload);\n\n if (picked.length === 0) {\n return this.miss(telemetry, 'no_items_above_threshold');\n }\n\n picked.sort((a, b) => b.score - a.score);\n const top = picked.slice(0, maxItems);\n\n const pickedStems = new Set<string>();\n for (const { item } of top) for (const t of item.tokens) pickedStems.add(t);\n let overlap = 0;\n for (const s of uniqueStems) if (pickedStems.has(s)) overlap++;\n const coverage = uniqueStems.size ? overlap / uniqueStems.size : 1.0;\n telemetry.coverage = round4(coverage);\n\n // Observe-only unless explicitly configured. A malformed setting stays\n // observe-only rather than failing closed on every lookup.\n const coverageMin = getEnvFloat('SYNAP_SDK_CACHE_COVERAGE_MIN');\n if (coverageMin !== null && coverage < coverageMin) {\n // Gate BEFORE any side effect, so a rejected lookup leaves cache state\n // untouched and does not refresh a lease.\n return this.miss(telemetry, 'coverage_gate');\n }\n\n const itemsByType: Record<string, BundleItem[]> = {};\n const bundleIds = new Set<string>();\n for (const { item } of top) {\n (itemsByType[item.itemType] ??= []).push(item.itemDict);\n bundleIds.add(item.bundleId);\n }\n\n // Honouring a TTL hint also means a hit stops renewing the lease, otherwise\n // a hinted bundle under steady traffic would outlive its hint.\n const refreshOnHit = !getEnvFlag('SYNAP_SDK_CACHE_HONOR_TTL_HINT');\n if (refreshOnHit) {\n const now = this.now();\n for (const id of bundleIds) {\n const entry = this.entries.get(id);\n if (entry) entry.storedAt = now;\n }\n }\n\n telemetry.hit = true;\n telemetry.exitReason = 'hit';\n telemetry.itemsPicked = top.map(({ score, item }) => ({\n bundle_id: item.bundleId, item_type: item.itemType,\n content_first_120: item.content.slice(0, PREVIEW_CHARS), bm25_score: round4(score),\n }));\n this.fire(telemetry);\n\n return {\n itemsByType,\n bundleIds: [...bundleIds],\n score: top[0]!.score,\n coverage,\n };\n }\n\n /**\n * Drop this entity's bundles after a write, so the cache stops serving a\n * pre-write view. Gated on the flag: this is the write-path hook.\n */\n invalidateEntity(entityId: string): number {\n if (!getEnvFlag('SYNAP_SDK_CACHE_INVALIDATE_ON_WRITE')) return 0;\n return this.dropEntity(entityId);\n }\n\n /**\n * Unconditionally drop this entity's bundles. Backs `cache.clear_user` and\n * `cache.clear_customer`, which are explicit user requests and so are not\n * gated on the invalidate-on-write flag.\n */\n dropEntity(entityId: string): number {\n let dropped = 0;\n for (const entry of [...this.entries.values()]) {\n // Never drop shared client-scope knowledge on one entity's behalf.\n if (entry.entityId === ANY_SCOPE) continue;\n if (entry.entityId === entityId) { this.dropBundle(entry.bundleId); dropped++; }\n }\n return dropped;\n }\n\n /**\n * A read-only diagnostic projection of the cache.\n *\n * Backs `client.anticipation_cache_snapshot()` and mirrors Python's shape\n * key for key, including snake_case keys: this output gets logged, diffed\n * and pasted into issues across both SDKs, so it is a wire format.\n *\n * Note the 140-character content previews. That is Python's literal, and it\n * is deliberately NOT the 120 of `telemetry.content_preview_chars` from the\n * contract, which caps lookup telemetry rather than this snapshot.\n */\n snapshot(): AnticipationCacheSnapshot {\n const SNAPSHOT_PREVIEW_CHARS = 140;\n const overallScope = new Map<string, number>();\n const itemsByBundle = new Map<string, ItemRecord[]>();\n for (const record of this.items) {\n const list = itemsByBundle.get(record.bundleId);\n if (list === undefined) itemsByBundle.set(record.bundleId, [record]);\n else list.push(record);\n }\n\n const bundles: SnapshotBundle[] = [];\n for (const [bundleId, entry] of this.entries) {\n const perBundle = new Map<string, number>();\n const itemPreviews: SnapshotItemPreview[] = [];\n for (const record of itemsByBundle.get(bundleId) ?? []) {\n // `scope` is absent on plenty of items; Python's Counter happily keys\n // on None, which JSON-serialises to null, so preserve the distinction\n // rather than coercing to a string.\n const scope = (record.itemDict['scope'] ?? null) as string | null;\n const key = scope === null ? '' : scope;\n overallScope.set(key, (overallScope.get(key) ?? 0) + 1);\n perBundle.set(key, (perBundle.get(key) ?? 0) + 1);\n itemPreviews.push({\n type: record.itemType,\n scope,\n content: record.content.slice(0, SNAPSHOT_PREVIEW_CHARS),\n });\n }\n let totalItems = 0;\n for (const n of perBundle.values()) totalItems += n;\n bundles.push({\n bundle_id: bundleId,\n entity_id: entry.entityId,\n conversation_id: entry.conversationId,\n bundle_type: entry.bundleType,\n search_queries: [...entry.searchQueries],\n scope_counts: Object.fromEntries(perBundle),\n total_items: totalItems,\n items: itemPreviews,\n });\n }\n\n return {\n total_entries: this.entries.size,\n total_item_records: this.items.length,\n scope_breakdown_overall: Object.fromEntries(overallScope),\n corpus_vocab_size: this.corpusVocab.size,\n corpus_vocab_sample: [...this.corpusVocab].sort().slice(0, 80),\n item_records: this.items.slice(0, 20).map((rec) => ({\n bundle_id: rec.bundleId,\n item_type: rec.itemType,\n tokens: rec.tokens.slice(0, 25),\n content: rec.content.slice(0, SNAPSHOT_PREVIEW_CHARS),\n })),\n bundles,\n };\n }\n\n /**\n * The freshest `user_summary` bundle for this entity, or null.\n *\n * `entityId` is REQUIRED, and a missing one refuses the lookup rather than\n * falling back to the freshest summary across all users. Python hardened this\n * for exactly that reason: the permissive version could splice user A's\n * summary into user B's response on the conversation-scope path.\n */\n lookupUserSummary(\n entityId: string | null | undefined,\n scopeRung: string | null = null,\n ): Record<string, unknown> | null {\n if (!entityId) return null;\n this.evictExpired();\n\n let freshest: EntryRecord | null = null;\n for (const entry of this.entries.values()) {\n if (entry.bundleType !== 'user_summary') continue;\n if (!rungServes(entry.scopeRung, scopeRung)) continue;\n // ANY_SCOPE covers client-shared summaries, which are safe for everyone.\n if (entry.entityId !== entityId && entry.entityId !== ANY_SCOPE) continue;\n if (freshest === null || entry.storedAt > freshest.storedAt) freshest = entry;\n }\n if (freshest === null) return null;\n\n // Serving refreshes the sliding lease, unless the server's TTL hint is\n // being honoured, in which case the hint owns expiry.\n if (!getEnvFlag('SYNAP_SDK_CACHE_HONOR_TTL_HINT')) {\n freshest.storedAt = this.now();\n }\n return this.#bundleFor(freshest.bundleId);\n }\n\n /** Rebuild a bundle's items_by_type from the flat item records. */\n #bundleFor(bundleId: string): Record<string, unknown> {\n const itemsByType: Record<string, BundleItem[]> = {};\n for (const record of this.items) {\n if (record.bundleId !== bundleId) continue;\n (itemsByType[record.itemType] ??= []).push(record.itemDict);\n }\n return { bundle_id: bundleId, items_by_type: itemsByType };\n }\n\n /**\n * Drop every bundle belonging to one conversation.\n *\n * Backs the `compaction_update` signal on the Listen stream: a compaction\n * rewrites the conversation's summary, so any bundle built from the previous\n * state is stale and must not keep serving for the rest of its TTL.\n *\n * Unlike `dropEntity`, client-scope bundles are NOT spared: a bundle is only\n * dropped here if it is bound to this specific conversation, and a shared\n * client-scope bundle has no conversation id to match.\n */\n dropConversation(conversationId: string): number {\n if (!conversationId) return 0;\n let dropped = 0;\n for (const entry of [...this.entries.values()]) {\n if (entry.conversationId === conversationId) { this.dropBundle(entry.bundleId); dropped++; }\n }\n return dropped;\n }\n\n clear(): void {\n this.entries.clear();\n this.items = [];\n this.corpusVocab.clear();\n this.bm25 = null;\n this.bm25Dirty = true;\n }\n\n // ── internals ───────────────────────────────────────────────────────────────\n\n private miss(telemetry: LookupTelemetry, reason: ExitReason): null {\n telemetry.exitReason = reason;\n telemetry.hit = false;\n this.fire(telemetry);\n return null;\n }\n\n private fire(telemetry: LookupTelemetry): void {\n // Exception-safe by contract: a buggy hook must never break the SDK.\n try {\n this.onLookup?.(telemetry);\n } catch {\n /* ignored on purpose */\n }\n }\n\n private freshnessLookup(\n entityId: string | null,\n scopeRung: string | null,\n maxItems: number,\n telemetry: LookupTelemetry,\n ): LookupResult | null {\n // This path serves a WHOLE bundle on no query at all, so it is the least\n // filtered way out of this cache and needs the rung as much as the scored\n // path does.\n const candidates = [...this.entries.values()]\n .filter((e) => rungServes(e.scopeRung, scopeRung))\n .filter((e) => entityId === null || e.entityId === entityId || e.entityId === ANY_SCOPE)\n .sort((a, b) => b.storedAt - a.storedAt);\n if (candidates.length === 0) return this.miss(telemetry, 'empty');\n\n const newest = candidates[0]!;\n const itemsByType: Record<string, BundleItem[]> = {};\n let n = 0;\n for (const item of this.items) {\n if (item.bundleId !== newest.bundleId) continue;\n if (n >= maxItems) break;\n (itemsByType[item.itemType] ??= []).push(item.itemDict);\n n++;\n }\n if (n === 0) return this.miss(telemetry, 'empty');\n\n telemetry.hit = true;\n telemetry.exitReason = 'hit';\n this.fire(telemetry);\n return { itemsByType, bundleIds: [newest.bundleId], score: 0, coverage: 1.0 };\n }\n\n /**\n * The widened scope-match set for a request.\n *\n * A narrower-scope request also matches broader-scope bundles for the same\n * customer or client. Falsy ids are dropped so an empty customer_id cannot\n * accidentally match bundles keyed at \"\".\n */\n private buildAcceptedScope(\n entityId: string | null, customerId: string | null, clientId: string | null,\n ): Set<string> {\n const accepted = new Set<string>([ANY_SCOPE]);\n if (entityId) accepted.add(entityId);\n if (customerId) accepted.add(customerId);\n if (clientId) accepted.add(clientId);\n return accepted;\n }\n\n /**\n * The rung is checked FIRST and it never widens. Everything else here widens\n * deliberately: a user-scope request reaches customer-shared bundles, and a\n * conversation-scope request reaches user-scope ones. A rung is the opposite\n * kind of thing, so it is checked before the tiers and no amount of widening\n * can put a cross-rung bundle back in.\n */\n private getValidBundleIds(\n accepted: Set<string>, conversationId: string | null, scopeRung: string | null,\n ): Set<string> {\n const valid = new Set<string>();\n for (const entry of this.entries.values()) {\n if (!rungServes(entry.scopeRung, scopeRung)) continue;\n if (!accepted.has(entry.entityId)) continue;\n // A conversation-scoped bundle is only valid inside its conversation.\n if (entry.conversationId && conversationId && entry.conversationId !== conversationId) continue;\n valid.add(entry.bundleId);\n }\n return valid;\n }\n\n private effectiveTtl(entry: EntryRecord): number {\n if (!getEnvFlag('SYNAP_SDK_CACHE_HONOR_TTL_HINT')) return this.ttlSeconds;\n if (entry.ttlHintSeconds == null || entry.ttlHintSeconds <= 0) return this.ttlSeconds;\n // A hint may only shorten.\n return Math.min(this.ttlSeconds, entry.ttlHintSeconds);\n }\n\n private evictExpired(): void {\n const now = this.now();\n // Default cap is twice the sliding TTL; 0 disables. Without it a bundle\n // under steady traffic renews forever and can serve a retired value\n // indefinitely.\n const configuredMaxAge = getEnvFloat('SYNAP_SDK_CACHE_MAX_ENTRY_AGE');\n const maxAge = configuredMaxAge ?? this.ttlSeconds * 2;\n\n for (const entry of [...this.entries.values()]) {\n const idleExpired = now - entry.storedAt > this.effectiveTtl(entry);\n const ageExpired = maxAge > 0 && now - entry.createdAt > maxAge;\n if (idleExpired || ageExpired) this.dropBundle(entry.bundleId);\n }\n }\n\n private dropBundle(bundleId: string): void {\n if (!this.entries.delete(bundleId)) return;\n this.items = this.items.filter((i) => i.bundleId !== bundleId);\n // Vocabulary is rebuilt rather than decremented: a stem can be shared by\n // several bundles, so per-bundle subtraction would drop stems still in use.\n this.corpusVocab = new Set(this.items.flatMap((i) => i.tokens));\n this.bm25Dirty = true;\n }\n}\n\nfunction monotonicNow(): number {\n // performance.now() is monotonic and unaffected by wall-clock adjustments,\n // matching Python's time.monotonic(). Date.now() would let an NTP step\n // expire or resurrect every entry at once.\n return typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now() / 1000\n : Date.now() / 1000;\n}\n\nfunction round4(n: number): number {\n return Number(n.toFixed(4));\n}\n"]}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
var _chunkT372OL3Qcjs = require('./chunk-T372OL3Q.cjs');
|
|
6
6
|
|
|
7
7
|
// src/grpc/descriptor.ts
|
|
8
|
-
var PROTO_SHA = "
|
|
8
|
+
var PROTO_SHA = "a0813b78a8c5d8dc";
|
|
9
9
|
var SYNAP_PROTO_DESCRIPTOR = {
|
|
10
10
|
"nested": {
|
|
11
11
|
"synap": {
|
|
@@ -97,6 +97,10 @@ var SYNAP_PROTO_DESCRIPTOR = {
|
|
|
97
97
|
"rule": "repeated",
|
|
98
98
|
"type": "string",
|
|
99
99
|
"id": 8
|
|
100
|
+
},
|
|
101
|
+
"query_count": {
|
|
102
|
+
"type": "int32",
|
|
103
|
+
"id": 9
|
|
100
104
|
}
|
|
101
105
|
}
|
|
102
106
|
},
|
|
@@ -1389,4 +1393,4 @@ var GrpcStreamClient = class {
|
|
|
1389
1393
|
|
|
1390
1394
|
|
|
1391
1395
|
exports.PROTO_SHA = PROTO_SHA; exports.CREDIT_ABORT_DETAILS = CREDIT_ABORT_DETAILS; exports.CREDIT_STOP_REASONS = CREDIT_STOP_REASONS; exports.creditErrorFromRpc = creditErrorFromRpc; exports.isCreditStop = isCreditStop; exports.GrpcStreamClient = GrpcStreamClient;
|
|
1392
|
-
//# sourceMappingURL=chunk-
|
|
1396
|
+
//# sourceMappingURL=chunk-WE4QSR6A.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/private/tmp/claude-501/-Users-anish-maximemai-maximem-synap/364ae6d0-b8b7-4119-bfaf-7676354661c6/scratchpad/jsrel/synap/sdk/js/dist/chunk-WE4QSR6A.cjs","../src/grpc/descriptor.ts","../src/grpc/outbox-journal.ts","../src/grpc/stream-client.ts"],"names":[],"mappings":"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","file":"/private/tmp/claude-501/-Users-anish-maximemai-maximem-synap/364ae6d0-b8b7-4119-bfaf-7676354661c6/scratchpad/jsrel/synap/sdk/js/dist/chunk-WE4QSR6A.cjs","sourcesContent":[null,"/**\n * GENERATED FILE. Do not edit by hand.\n *\n * Source: synap/proto/synap_service.proto\n * Generator: synap/sdk/scripts/gen_proto_descriptor.mjs\n * Proto SHA: a0813b78a8c5d8dc\n *\n * The proto is inlined as a JSON descriptor rather than loaded from disk so\n * that bundlers and Vercel file tracing cannot lose it. Regenerate with\n * `npm run sync-behavior` in synap/sdk/js.\n */\n\n/** sha256 (first 16 hex chars) of the proto this descriptor was built from. */\nexport const PROTO_SHA = 'a0813b78a8c5d8dc';\n\nexport const SYNAP_PROTO_DESCRIPTOR = {\n \"nested\": {\n \"synap\": {\n \"nested\": {\n \"v1\": {\n \"nested\": {\n \"SynapService\": {\n \"methods\": {\n \"Listen\": {\n \"requestType\": \"StreamEvent\",\n \"requestStream\": true,\n \"responseType\": \"StreamResponse\",\n \"responseStream\": true\n },\n \"IngestTelemetry\": {\n \"requestType\": \"TelemetryEvent\",\n \"requestStream\": true,\n \"responseType\": \"TelemetryAck\"\n }\n }\n },\n \"StreamEvent\": {\n \"oneofs\": {\n \"payload\": {\n \"oneof\": [\n \"conversation_event\",\n \"heartbeat_ping\",\n \"session_control\",\n \"context_used\",\n \"context_assembled\"\n ]\n }\n },\n \"fields\": {\n \"conversation_event\": {\n \"type\": \"ConversationEvent\",\n \"id\": 1\n },\n \"heartbeat_ping\": {\n \"type\": \"HeartbeatPing\",\n \"id\": 2\n },\n \"session_control\": {\n \"type\": \"SessionControl\",\n \"id\": 3\n },\n \"context_used\": {\n \"type\": \"ContextUsedEvent\",\n \"id\": 4\n },\n \"context_assembled\": {\n \"type\": \"ContextAssembledEvent\",\n \"id\": 5\n }\n }\n },\n \"ContextUsedEvent\": {\n \"fields\": {\n \"bundle_id\": {\n \"type\": \"string\",\n \"id\": 1\n },\n \"conversation_id\": {\n \"type\": \"string\",\n \"id\": 2\n },\n \"user_id\": {\n \"type\": \"string\",\n \"id\": 3\n },\n \"customer_id\": {\n \"type\": \"string\",\n \"id\": 4\n },\n \"served_item_ids\": {\n \"rule\": \"repeated\",\n \"type\": \"string\",\n \"id\": 5\n },\n \"timestamp_ms\": {\n \"type\": \"int64\",\n \"id\": 6\n },\n \"scope\": {\n \"type\": \"string\",\n \"id\": 7\n },\n \"source_bundle_ids\": {\n \"rule\": \"repeated\",\n \"type\": \"string\",\n \"id\": 8\n },\n \"query_count\": {\n \"type\": \"int32\",\n \"id\": 9\n }\n }\n },\n \"ContextAssembledEvent\": {\n \"fields\": {\n \"correlation_id\": {\n \"type\": \"string\",\n \"id\": 1\n },\n \"conversation_id\": {\n \"type\": \"string\",\n \"id\": 2\n },\n \"user_id\": {\n \"type\": \"string\",\n \"id\": 3\n },\n \"customer_id\": {\n \"type\": \"string\",\n \"id\": 4\n },\n \"final_item_ids\": {\n \"rule\": \"repeated\",\n \"type\": \"string\",\n \"id\": 5\n },\n \"final_total_tokens\": {\n \"type\": \"int32\",\n \"id\": 6\n },\n \"compaction_id\": {\n \"type\": \"string\",\n \"id\": 7\n },\n \"recent_turn_count\": {\n \"type\": \"int32\",\n \"id\": 8\n },\n \"compaction_end_timestamp\": {\n \"type\": \"string\",\n \"id\": 9\n },\n \"assembly_source\": {\n \"type\": \"string\",\n \"id\": 10\n },\n \"assembly_duration_ms\": {\n \"type\": \"int32\",\n \"id\": 11\n },\n \"cache_hit\": {\n \"type\": \"bool\",\n \"id\": 12\n },\n \"timestamp_ms\": {\n \"type\": \"int64\",\n \"id\": 13\n },\n \"sdk_version\": {\n \"type\": \"string\",\n \"id\": 14\n }\n }\n },\n \"ConversationEvent\": {\n \"fields\": {\n \"event_type\": {\n \"type\": \"string\",\n \"id\": 1\n },\n \"conversation_id\": {\n \"type\": \"string\",\n \"id\": 2\n },\n \"user_id\": {\n \"type\": \"string\",\n \"id\": 3\n },\n \"role\": {\n \"type\": \"string\",\n \"id\": 4\n },\n \"content\": {\n \"type\": \"string\",\n \"id\": 5\n },\n \"customer_id\": {\n \"type\": \"string\",\n \"id\": 6\n },\n \"session_id\": {\n \"type\": \"string\",\n \"id\": 7\n },\n \"metadata\": {\n \"keyType\": \"string\",\n \"type\": \"string\",\n \"id\": 8\n },\n \"timestamp_ms\": {\n \"type\": \"int64\",\n \"id\": 9\n },\n \"tool_name\": {\n \"type\": \"string\",\n \"id\": 10\n },\n \"tool_args_json\": {\n \"type\": \"string\",\n \"id\": 11\n },\n \"tool_result_json\": {\n \"type\": \"string\",\n \"id\": 15\n },\n \"tool_call_id\": {\n \"type\": \"string\",\n \"id\": 16\n },\n \"search_queries\": {\n \"rule\": \"repeated\",\n \"type\": \"string\",\n \"id\": 12\n },\n \"context_types\": {\n \"rule\": \"repeated\",\n \"type\": \"string\",\n \"id\": 13\n },\n \"scope\": {\n \"keyType\": \"string\",\n \"type\": \"string\",\n \"id\": 14\n },\n \"event_id\": {\n \"type\": \"string\",\n \"id\": 17\n },\n \"sent_at_ms\": {\n \"type\": \"int64\",\n \"id\": 18\n }\n }\n },\n \"HeartbeatPing\": {\n \"fields\": {\n \"timestamp_ms\": {\n \"type\": \"int64\",\n \"id\": 1\n }\n }\n },\n \"SessionControl\": {\n \"fields\": {\n \"action\": {\n \"type\": \"string\",\n \"id\": 1\n },\n \"session_id\": {\n \"type\": \"string\",\n \"id\": 2\n },\n \"conversation_id\": {\n \"type\": \"string\",\n \"id\": 3\n },\n \"user_id\": {\n \"type\": \"string\",\n \"id\": 4\n },\n \"customer_id\": {\n \"type\": \"string\",\n \"id\": 5\n }\n }\n },\n \"StreamResponse\": {\n \"oneofs\": {\n \"payload\": {\n \"oneof\": [\n \"context_bundle\",\n \"heartbeat_pong\",\n \"signal\",\n \"event_ack\"\n ]\n }\n },\n \"fields\": {\n \"context_bundle\": {\n \"type\": \"ContextBundleProto\",\n \"id\": 1\n },\n \"heartbeat_pong\": {\n \"type\": \"HeartbeatPong\",\n \"id\": 2\n },\n \"signal\": {\n \"type\": \"StreamSignal\",\n \"id\": 3\n },\n \"event_ack\": {\n \"type\": \"EventAck\",\n \"id\": 4\n }\n }\n },\n \"EventAck\": {\n \"fields\": {\n \"event_ids\": {\n \"rule\": \"repeated\",\n \"type\": \"string\",\n \"id\": 1\n },\n \"timestamp_ms\": {\n \"type\": \"int64\",\n \"id\": 2\n }\n }\n },\n \"HeartbeatPong\": {\n \"fields\": {\n \"timestamp_ms\": {\n \"type\": \"int64\",\n \"id\": 1\n }\n }\n },\n \"StreamSignal\": {\n \"fields\": {\n \"signal_type\": {\n \"type\": \"string\",\n \"id\": 1\n },\n \"reason\": {\n \"type\": \"string\",\n \"id\": 2\n },\n \"metadata\": {\n \"keyType\": \"string\",\n \"type\": \"string\",\n \"id\": 3\n }\n }\n },\n \"ContextBundleProto\": {\n \"fields\": {\n \"bundle_id\": {\n \"type\": \"string\",\n \"id\": 1\n },\n \"decision_id\": {\n \"type\": \"string\",\n \"id\": 2\n },\n \"items_by_type\": {\n \"keyType\": \"string\",\n \"type\": \"ContextItemList\",\n \"id\": 3\n },\n \"total_tokens\": {\n \"type\": \"int32\",\n \"id\": 4\n },\n \"token_budget\": {\n \"type\": \"int32\",\n \"id\": 5\n },\n \"budget_exceeded\": {\n \"type\": \"bool\",\n \"id\": 6\n },\n \"retrieval_mode\": {\n \"type\": \"string\",\n \"id\": 7\n },\n \"sources_queried\": {\n \"rule\": \"repeated\",\n \"type\": \"string\",\n \"id\": 8\n },\n \"degradation_level\": {\n \"type\": \"string\",\n \"id\": 9\n },\n \"warnings\": {\n \"rule\": \"repeated\",\n \"type\": \"string\",\n \"id\": 10\n },\n \"created_at\": {\n \"type\": \"string\",\n \"id\": 11\n },\n \"retrieval_time_ms\": {\n \"type\": \"int32\",\n \"id\": 12\n },\n \"cache_hit\": {\n \"type\": \"bool\",\n \"id\": 13\n },\n \"search_queries\": {\n \"rule\": \"repeated\",\n \"type\": \"string\",\n \"id\": 14\n },\n \"anticipation_user_id\": {\n \"type\": \"string\",\n \"id\": 15\n },\n \"anticipation_customer_id\": {\n \"type\": \"string\",\n \"id\": 16\n },\n \"anticipation_conversation_id\": {\n \"type\": \"string\",\n \"id\": 17\n },\n \"search_keywords\": {\n \"rule\": \"repeated\",\n \"type\": \"string\",\n \"id\": 18\n },\n \"bundle_type\": {\n \"type\": \"string\",\n \"id\": 19\n },\n \"conversation_context\": {\n \"type\": \"ConversationContextProto\",\n \"id\": 20\n },\n \"bundle_confidence\": {\n \"type\": \"float\",\n \"id\": 21\n },\n \"origin_pattern_id\": {\n \"type\": \"string\",\n \"id\": 22\n },\n \"ttl_hint_seconds\": {\n \"type\": \"int32\",\n \"id\": 23\n },\n \"anticipation_scope_rung\": {\n \"type\": \"string\",\n \"id\": 24\n }\n }\n },\n \"ConversationContextProto\": {\n \"fields\": {\n \"summary\": {\n \"type\": \"string\",\n \"id\": 1\n },\n \"current_state_json\": {\n \"type\": \"string\",\n \"id\": 2\n },\n \"key_extractions_json\": {\n \"type\": \"string\",\n \"id\": 3\n },\n \"recent_turns\": {\n \"rule\": \"repeated\",\n \"type\": \"RecentTurnProto\",\n \"id\": 4\n },\n \"compaction_id\": {\n \"type\": \"string\",\n \"id\": 5\n },\n \"compacted_at\": {\n \"type\": \"string\",\n \"id\": 6\n },\n \"conversation_id\": {\n \"type\": \"string\",\n \"id\": 7\n }\n }\n },\n \"RecentTurnProto\": {\n \"fields\": {\n \"role\": {\n \"type\": \"string\",\n \"id\": 1\n },\n \"content\": {\n \"type\": \"string\",\n \"id\": 2\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"id\": 3\n }\n }\n },\n \"ContextItemList\": {\n \"fields\": {\n \"items\": {\n \"rule\": \"repeated\",\n \"type\": \"ContextItemProto\",\n \"id\": 1\n }\n }\n },\n \"ContextItemProto\": {\n \"fields\": {\n \"item_id\": {\n \"type\": \"string\",\n \"id\": 1\n },\n \"content\": {\n \"type\": \"string\",\n \"id\": 2\n },\n \"context_type\": {\n \"type\": \"string\",\n \"id\": 3\n },\n \"source\": {\n \"type\": \"string\",\n \"id\": 4\n },\n \"similarity_score\": {\n \"type\": \"float\",\n \"id\": 5\n },\n \"relevance_score\": {\n \"type\": \"float\",\n \"id\": 6\n },\n \"confidence\": {\n \"type\": \"float\",\n \"id\": 7\n },\n \"scope\": {\n \"type\": \"string\",\n \"id\": 8\n },\n \"entity_id\": {\n \"type\": \"string\",\n \"id\": 9\n },\n \"created_at\": {\n \"type\": \"string\",\n \"id\": 10\n },\n \"event_date\": {\n \"type\": \"string\",\n \"id\": 11\n },\n \"valid_until\": {\n \"type\": \"string\",\n \"id\": 12\n },\n \"temporal_category\": {\n \"type\": \"string\",\n \"id\": 13\n },\n \"temporal_confidence\": {\n \"type\": \"float\",\n \"id\": 14\n }\n }\n },\n \"TelemetryEvent\": {\n \"fields\": {\n \"event_type\": {\n \"type\": \"string\",\n \"id\": 1\n },\n \"instance_id\": {\n \"type\": \"string\",\n \"id\": 2\n },\n \"client_id\": {\n \"type\": \"string\",\n \"id\": 3\n },\n \"correlation_id\": {\n \"type\": \"string\",\n \"id\": 4\n },\n \"timestamp_ms\": {\n \"type\": \"int64\",\n \"id\": 5\n },\n \"latency_ms\": {\n \"type\": \"int32\",\n \"id\": 6\n },\n \"status\": {\n \"type\": \"string\",\n \"id\": 7\n },\n \"error_code\": {\n \"type\": \"string\",\n \"id\": 8\n },\n \"scope\": {\n \"type\": \"string\",\n \"id\": 9\n },\n \"cache_status\": {\n \"type\": \"string\",\n \"id\": 10\n },\n \"attempt\": {\n \"type\": \"int32\",\n \"id\": 11\n },\n \"http_method\": {\n \"type\": \"string\",\n \"id\": 12\n },\n \"http_path\": {\n \"type\": \"string\",\n \"id\": 13\n },\n \"http_status_code\": {\n \"type\": \"int32\",\n \"id\": 14\n },\n \"metadata\": {\n \"keyType\": \"string\",\n \"type\": \"string\",\n \"id\": 15\n },\n \"sdk_version\": {\n \"type\": \"string\",\n \"id\": 16\n },\n \"batch_id\": {\n \"type\": \"string\",\n \"id\": 17\n }\n }\n },\n \"TelemetryAck\": {\n \"fields\": {\n \"status\": {\n \"type\": \"string\",\n \"id\": 1\n },\n \"events_received\": {\n \"type\": \"int32\",\n \"id\": 2\n }\n }\n }\n }\n }\n }\n }\n }\n} as const;\n","/**\n * Disk backing for the stream client's outbound buffers.\n *\n * The send queue and the unacknowledged map already survive a dropped\n * connection. Neither survives the process: both live in memory, so a hard\n * kill between a turn being recorded and the server acknowledging it loses\n * that turn with no trace anywhere.\n *\n * Every change to either buffer appends one line here, and the next start\n * reads the file back before the first event goes out. Mirrors Python's\n * `transport/outbox_journal.py` line for line, including the file format, so\n * a Python process and a Node process on the same storage root read each\n * other's journals.\n *\n * **Why a log and not a snapshot.** Rewriting both buffers on every change is\n * O(depth) per event and leaves a window between the change and the write.\n * Debouncing the write widens that window on purpose, which is the opposite of\n * the point. An append is one small line and happens before the caller is told\n * the event was accepted.\n *\n * **Why synchronous.** `appendFileSync`, not the promise form. An async write\n * still pending when the process dies is a write that did not happen, and this\n * file exists precisely for the case where the process dies.\n *\n * **Where it does not run.** Workers, edge runtimes and the browser have no\n * filesystem. `node:fs` is loaded through a guarded dynamic require and, when\n * it is not there, the journal disables itself and the SDK behaves exactly as\n * it did before: buffered events survive a reconnect and not a restart. That\n * is a real gap against Python and it is declared in SDK_PARITY.md rather than\n * papered over, because those runtimes have no disk to offer.\n */\n\n/** The ops, one character each because they are written once per event. */\nconst OP_QUEUED = 'q';\nconst OP_UNACKED = 'u';\nconst OP_ACKED = 'a';\nconst OP_DROPPED = 'd';\n\n/** Rewrite the file once it holds this many lines. The buffers are bounded at\n * 100 entries each, so past a few hundred lines it is mostly tombstones. */\nconst COMPACT_AT_LINES = 500;\n\ninterface NodeFs {\n appendFileSync: (p: string, data: string, enc: string) => void;\n readFileSync: (p: string, enc: string) => string;\n writeFileSync: (p: string, data: string, enc: string) => void;\n mkdirSync: (p: string, opts: { recursive: boolean }) => void;\n existsSync: (p: string) => boolean;\n renameSync: (a: string, b: string) => void;\n unlinkSync: (p: string) => void;\n}\n\n/**\n * `node:fs`, resolved once for the process.\n *\n * It has to be a dynamic `import()`, because this package ships an ESM build\n * and a CJS one and is also used on edge runtimes: a static import would make\n * the edge bundle fail to load over a module it can never use, and `require`\n * is not defined under ESM, which is what the test runner uses. A dynamic\n * import works in all three and simply rejects where there is no `node:fs`.\n *\n * Resolving it is therefore async, but every write here has to be synchronous\n * (an async write still pending when the process dies is a write that did not\n * happen). So it is resolved ONCE, up front, by `OutboxJournal.init()`, which\n * the stream client awaits inside `connect()` before any event can flow. After\n * that the module object is in hand and the appends are plain sync calls.\n */\nlet _fs: NodeFs | null = null;\nlet _fsResolved = false;\nlet _fsPending: Promise<void> | null = null;\n\nasync function resolveFs(): Promise<void> {\n if (_fsResolved) return;\n // Share one in-flight import. Two clients constructed at once would\n // otherwise each start their own, and the second could observe `_fs` still\n // null and disable itself for the life of the process.\n if (_fsPending === null) {\n _fsPending = (async () => {\n try {\n _fs = (await import('node:fs')) as unknown as NodeFs;\n } catch {\n _fs = null; // edge runtime, worker, browser\n }\n _fsResolved = true;\n })();\n }\n await _fsPending;\n}\n\nexport interface JournalEvent { event_id?: string; [k: string]: unknown }\n\n/**\n * `~/.synap`, the same root Python's SDK uses, so a Node process and a Python\n * process on one machine read each other's journals. Empty string where there\n * is no home directory to ask for, which disables the journal.\n */\nexport function defaultOutboxRoot(): string {\n try {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env;\n const home = env?.['SYNAP_STORAGE_PATH'] ?? env?.['HOME'] ?? env?.['USERPROFILE'];\n if (home !== undefined && home !== '') {\n return env?.['SYNAP_STORAGE_PATH'] !== undefined && env['SYNAP_STORAGE_PATH'] !== ''\n ? env['SYNAP_STORAGE_PATH'] : `${home}/.synap`;\n }\n } catch { /* no process object: edge runtime */ }\n return '';\n}\n\nexport class OutboxJournal {\n readonly path: string;\n #fs: NodeFs | null;\n #lines = 0;\n #disabled = false;\n\n /**\n * @param root Directory the journal lives under. One file per instance id,\n * because two clients on one instance share a stream in the\n * registry and so the events are the instance's, not the\n * object's.\n */\n /**\n * Resolve `node:fs` once for the process. The stream client awaits this in\n * `connect()` before any event can flow, so by the time anything is written\n * the module is in hand and every append is a plain synchronous call. An\n * async write still pending when the process dies is a write that did not\n * happen, and this file exists for exactly that case.\n */\n static async init(): Promise<void> {\n await resolveFs();\n }\n\n constructor(root: string, instanceId: string, fsImpl?: NodeFs | null) {\n // An empty root means there is nowhere to put this: no home directory, or\n // the caller turned it off. Without this guard the path became\n // `/outbox/<id>.jsonl`, which is a write to the filesystem root.\n this.path = root === '' ? '' : `${root}/outbox/${instanceId}.jsonl`;\n this.#fs = fsImpl !== undefined ? fsImpl : _fs;\n if (this.#fs === null || this.path === '') this.#disabled = true;\n }\n\n get enabled(): boolean { return !this.#disabled; }\n\n // ------------------------------------------------------------------ writes\n\n recordQueued(event: JournalEvent): void { this.#append({ op: OP_QUEUED, e: event }); }\n recordUnacked(event: JournalEvent): void { this.#append({ op: OP_UNACKED, e: event }); }\n recordAcked(id: string): void { this.#append({ op: OP_ACKED, id }); }\n\n /** An event the buffers gave up on. It does not come back on restart either:\n * replaying what the depth or age bound deliberately evicted would quietly\n * undo the bound that evicted it. */\n recordDropped(id: string | undefined): void {\n if (id === undefined || id === '') return;\n this.#append({ op: OP_DROPPED, id });\n }\n\n // ------------------------------------------------------------------- reads\n\n /**\n * Replay the file into `{queued, unacked}`, oldest first.\n *\n * A partial last line from a kill mid-append stops the read there. What came\n * before it is the prefix that was durably written; a torn line is an event\n * the caller was never told had been accepted.\n */\n load(): { queued: JournalEvent[]; unacked: JournalEvent[] } {\n const empty = { queued: [], unacked: [] };\n if (this.#disabled || this.#fs === null) return empty;\n let text: string;\n try {\n if (!this.#fs.existsSync(this.path)) return empty;\n text = this.#fs.readFileSync(this.path, 'utf8');\n } catch {\n return empty;\n }\n const queued = new Map<string, JournalEvent>();\n const unacked = new Map<string, JournalEvent>();\n let lines = 0;\n for (const raw of text.split('\\n')) {\n const line = raw.trim();\n if (line === '') continue;\n let rec: { op?: string; id?: string; e?: JournalEvent };\n try {\n rec = JSON.parse(line) as typeof rec;\n } catch {\n break; // torn last line; everything before it stands\n }\n lines += 1;\n const id = rec.op === OP_QUEUED || rec.op === OP_UNACKED\n ? rec.e?.event_id : rec.id;\n if (id === undefined || id === '') continue;\n if (rec.op === OP_QUEUED) {\n queued.set(id, rec.e as JournalEvent);\n } else if (rec.op === OP_UNACKED) {\n // It left the queue to be written, so it is no longer queued. Without\n // this a reconnect-then-kill replays it from both lists.\n queued.delete(id);\n unacked.set(id, rec.e as JournalEvent);\n } else if (rec.op === OP_ACKED || rec.op === OP_DROPPED) {\n queued.delete(id);\n unacked.delete(id);\n }\n }\n this.#lines = lines;\n return { queued: [...queued.values()], unacked: [...unacked.values()] };\n }\n\n // ------------------------------------------------------------- maintenance\n\n /**\n * Rewrite the file to hold exactly what the buffers hold now.\n *\n * Written beside the target and renamed over it, because rename is atomic: a\n * kill during a compaction leaves either the whole old file or the whole new\n * one, never a half-written mix.\n */\n compact(queued: JournalEvent[], unacked: JournalEvent[]): void {\n if (this.#disabled || this.#fs === null) return;\n if (queued.length === 0 && unacked.length === 0) { this.clear(); return; }\n try {\n this.#mkdir();\n const body = [\n ...unacked.map((e) => JSON.stringify({ op: OP_UNACKED, e })),\n ...queued.map((e) => JSON.stringify({ op: OP_QUEUED, e })),\n ].join('\\n') + '\\n';\n const tmp = `${this.path}.${Date.now()}.tmp`;\n this.#fs.writeFileSync(tmp, body, 'utf8');\n this.#fs.renameSync(tmp, this.path);\n this.#lines = queued.length + unacked.length;\n } catch {\n // Never break a turn or a close over bookkeeping.\n }\n }\n\n /** Nothing is outstanding, so there is nothing to replay. */\n clear(): void {\n if (this.#disabled || this.#fs === null) return;\n try { this.#fs.unlinkSync(this.path); } catch { /* already gone */ }\n this.#lines = 0;\n }\n\n get needsCompaction(): boolean { return this.#lines >= COMPACT_AT_LINES; }\n\n // ---------------------------------------------------------------- internal\n\n #mkdir(): void {\n const dir = this.path.slice(0, this.path.lastIndexOf('/'));\n this.#fs?.mkdirSync(dir, { recursive: true });\n }\n\n #append(record: Record<string, unknown>): void {\n if (this.#disabled || this.#fs === null) return;\n try {\n this.#mkdir();\n this.#fs.appendFileSync(this.path, `${JSON.stringify(record)}\\n`, 'utf8');\n this.#lines += 1;\n } catch (err) {\n // One warning, then stay quiet: a read-only or full filesystem would\n // otherwise log once per event for the life of the process.\n console.warn(\n `[synap] outbox journal write failed (${String(err)}); buffered events `\n + 'will not survive a restart from here on',\n );\n this.#disabled = true;\n }\n }\n}\n","/**\n * Bidirectional gRPC stream client for the Synap anticipation channel.\n *\n * Mirrors Python's `GRPCTransport` and the synap-vercel-adk client:\n * - `/synap.v1.SynapService/Listen`, StreamEvent in, StreamResponse out\n * - Auth via `authorization: Bearer <key>` plus `x-client-id` / `x-instance-id`\n * - 30s heartbeat, exponential-backoff reconnect\n * - Server pushes ContextBundleProto, which lands in the AnticipationCache\n *\n * Node-only, and reached exclusively through a lazy `import()` so that merely\n * importing the SDK on Edge or in a Worker stays safe. grpc-js is built on\n * `node:http2`/`node:net`/`node:tls`; a static import fails the BUILD there.\n *\n * The proto arrives as an inlined descriptor (see ./descriptor.ts) rather than\n * being read from disk, so bundlers and Vercel file tracing cannot lose it.\n */\n\nimport type { AnticipationCache } from '../context/anticipation-cache.js';\nimport { SYNAP_PROTO_DESCRIPTOR } from './descriptor.js';\nimport { OutboxJournal, defaultOutboxRoot } from './outbox-journal.js';\nimport { newCorrelationId } from '../util/correlation.js';\nimport { InsufficientCreditsError, RateLimitError, type SynapError } from '../errors.js';\n\nexport type StreamState =\n | 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'closed';\n\n/**\n * The ONLY bundle type the cache refuses.\n *\n * NOTE: this deliberately differs from the synap-vercel-adk stream client,\n * which caches an allowlist of `anticipation` and `user_summary` and drops\n * compaction updates. Python's `_handle_anticipated_bundle` stores everything\n * except `reactive`, and Python is the reference implementation, so an\n * allowlist here would silently cache less than the pip SDK does.\n *\n * Reactive bundles are excluded because they answer a fetch that already\n * happened; caching one serves a stale answer to a different question.\n */\nconst UNCACHEABLE_BUNDLE_TYPE = 'reactive';\n\n// Mirrors the Python GRPCTransport constants.\nconst MAX_RECONNECT_ATTEMPTS = 10;\nconst BACKOFF_BASE_MS = 1_000;\nconst BACKOFF_MAX_MS = 30_000;\nconst HEARTBEAT_INTERVAL_MS = 30_000;\nconst HEARTBEAT_TIMEOUT_MS = 10_000;\nconst MAX_MISSED_HEARTBEATS = 3;\n\n// The outbound buffer, same shape and same numbers as Python's transport\n// (SEND_QUEUE_MAX_DEPTH / SEND_QUEUE_MAX_AGE). Events written while the stream\n// is down were silently dropped here: `#write` returned early and said nothing,\n// so a reconnect lost every turn that happened during it.\nconst SEND_QUEUE_MAX_DEPTH = 100;\n// How long disconnect() waits for the server to finish reading after the\n// half-close. Matches Python's CLOSE_GRACE.\nconst CLOSE_GRACE_MS = 2_000;\nconst SEND_QUEUE_MAX_AGE_MS = 5 * 60 * 1_000;\n// How long disconnect() waits for the buffer to go out. Short: a shutdown path\n// that blocks is worse than a lost event.\nconst CLOSE_FLUSH_TIMEOUT_MS = 2_000;\n/** The server may only SHORTEN the SDK default, never extend it past this floor. */\nconst MIN_TTL_SECONDS = 60;\n\nexport interface StreamCredentials {\n apiKey: string;\n clientId: string;\n instanceId: string;\n}\n\nexport interface StreamClientOptions {\n host?: string;\n port?: number;\n useTls?: boolean;\n /** Injected in tests so reconnect backoff does not make the suite slow. */\n sleep?: (ms: number) => Promise<void>;\n /** How long to wait for the channel to become ready. Default 10s. */\n connectTimeoutMs?: number;\n onStateChange?: (state: StreamState) => void;\n /** Fires for every bundle, before the cache filter. Backs `listen({onContext})`. */\n onContext?: (bundle: Record<string, unknown>) => void;\n /** Fires only for `compaction_update` bundles. Backs the compaction subscriptions. */\n onCompactionUpdate?: (conversationId: string, bundle: Record<string, unknown>) => void;\n /**\n * Where the outbox journal lives, so buffered events survive a restart.\n * Defaults to `~/.synap`, the same root Python uses. `''` turns it off, and\n * it is off anyway wherever there is no filesystem.\n */\n outboxPath?: string;\n}\n\n/**\n * grpc-js status code for RESOURCE_EXHAUSTED. Hard-coded rather than read from\n * `grpc.status`, because this module is loaded before grpc-js on some paths and\n * the numbers are fixed by the gRPC spec.\n */\nconst GRPC_RESOURCE_EXHAUSTED = 8;\n\n/**\n * The credit gate refuses a call with RESOURCE_EXHAUSTED, these details, and\n * the balance and reason in trailing metadata. See\n * synap/cloud/application/credits/grpc_gate.py.\n */\nexport const CREDIT_ABORT_DETAILS = 'insufficient_credits';\n\n/**\n * Reasons that end the stream. A reconnect re-runs the same gate with the same\n * balance, so retrying only delays the answer the caller needs. Mirrors\n * `CREDIT_STOP_REASONS` in `transport/grpc_client.py`.\n */\nexport const CREDIT_STOP_REASONS = new Set([\n 'overages_disabled',\n 'trial_limit_reached',\n 'subscription_inactive',\n]);\n\nfunction metadataValue(metadata: unknown, key: string): string | null {\n if (metadata === null || typeof metadata !== 'object') return null;\n const getter = (metadata as { get?: (k: string) => unknown }).get;\n const raw = typeof getter === 'function'\n ? getter.call(metadata, key)\n : (metadata as Record<string, unknown>)[key];\n const value = Array.isArray(raw) ? raw[0] : raw;\n if (value === undefined || value === null) return null;\n const text = typeof value === 'string' ? value : String(value);\n return text === '' ? null : text;\n}\n\nfunction numberOrNull(value: string | null): number | null {\n if (value === null) return null;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\n/**\n * Translate a credit refusal into the same error HTTP would have raised.\n *\n * RESOURCE_EXHAUSTED on its own is indistinguishable from server overload, so\n * this keys on `credit-reason`: `overages_disabled` is an\n * `InsufficientCreditsError` (permanent, a paid plan at zero that has not\n * allowed usage past zero), while `trial_limit_reached` and\n * `subscription_inactive` are a `RateLimitError`. Returns null for anything\n * else, including a RESOURCE_EXHAUSTED that is not a credit refusal and a\n * reason this SDK does not know: the caller then keeps its existing behaviour\n * of treating the abort as transient. Mirrors `credit_error_from_rpc`.\n */\nexport function creditErrorFromRpc(\n error: unknown,\n correlationId: string | null = null,\n): SynapError | null {\n if (error === null || typeof error !== 'object') return null;\n if ((error as { code?: unknown }).code !== GRPC_RESOURCE_EXHAUSTED) return null;\n\n const metadata = (error as { metadata?: unknown }).metadata;\n const reason = metadataValue(metadata, 'credit-reason');\n if (reason === null || !CREDIT_STOP_REASONS.has(reason)) return null;\n\n const rawDetails = (error as { details?: unknown }).details;\n const details = typeof rawDetails === 'string' && rawDetails !== ''\n ? rawDetails\n : CREDIT_ABORT_DETAILS;\n const requestId = metadataValue(metadata, 'request-id') ?? correlationId;\n const manageUrl = metadataValue(metadata, 'credit-manage-url');\n\n if (reason === 'overages_disabled') {\n return new InsufficientCreditsError(details, {\n correlationId: requestId,\n balanceCredits: numberOrNull(metadataValue(metadata, 'credit-balance')),\n requiredCredits: numberOrNull(metadataValue(metadata, 'credit-minimum-required')),\n recoveryUrl: metadataValue(metadata, 'credit-recovery-url'),\n redeemUrl: metadataValue(metadata, 'credit-redeem-url'),\n reason,\n manageUrl,\n });\n }\n\n return new RateLimitError(details, {\n correlationId: requestId,\n reason,\n upgradeUrl: metadataValue(metadata, 'credit-upgrade-url'),\n manageUrl,\n });\n}\n\n/** True for an error this module raised from a credit refusal. */\nexport function isCreditStop(error: unknown): boolean {\n if (!(error instanceof InsufficientCreditsError) && !(error instanceof RateLimitError)) {\n return false;\n }\n const reason = (error as { reason?: unknown }).reason;\n return typeof reason === 'string' && CREDIT_STOP_REASONS.has(reason);\n}\n\ninterface DuplexCall {\n write: (msg: unknown) => void;\n on: (event: string, handler: (arg?: unknown) => void) => void;\n end: () => void;\n}\n\nexport interface ConversationEventInput {\n event_type: string;\n conversation_id?: string;\n user_id?: string;\n customer_id?: string;\n role?: string;\n content?: string;\n session_id?: string;\n metadata?: Record<string, string>;\n timestamp_ms?: number;\n /** proto: ConversationEvent.tool_name */\n tool_name?: string;\n /** proto: ConversationEvent.tool_args_json. Python sends json.dumps(tool_args). */\n tool_args_json?: string;\n /**\n * proto: ConversationEvent.tool_result_json. A result is not a message, so\n * it does not ride in `content`; a string is sent as itself rather than as a\n * quoted JSON string, matching Python.\n */\n tool_result_json?: string;\n /** proto: ConversationEvent.tool_call_id. Ties a result to its call. */\n tool_call_id?: string;\n search_queries?: string[];\n context_types?: string[];\n /**\n * proto: ConversationEvent.event_id. Minted here when absent, and the SAME\n * id travels on every replay — that is what the server's dedupe matches, so\n * a retry cannot double a turn.\n */\n event_id?: string;\n /** proto: ConversationEvent.sent_at_ms. When we FIRST tried, not when it happened. */\n sent_at_ms?: number;\n}\n\nexport class GrpcStreamClient {\n readonly host: string;\n readonly port: number;\n readonly useTls: boolean;\n\n #state: StreamState = 'disconnected';\n #call: DuplexCall | null = null;\n #reconnectAttempts = 0;\n #lastPongAt = 0;\n #missedHeartbeats = 0;\n #heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n #shutdownRequested = false;\n // Events that could not be written because there was no stream.\n #sendQueue: Array<{ queuedAt: number; event: ConversationEventInput }> = [];\n // Events written to a live stream that the server has not acknowledged. The\n // queue above covers \"the stream was down\"; this covers the half that\n // actually loses sessions — the write succeeded, the stream broke before the\n // server processed it, and nobody knows.\n #unacked = new Map<string, { queuedAt: number; event: ConversationEventInput }>();\n // Why the stream stopped, when it stopped for a reason the caller can act on\n // (today: a credit refusal). Mirrors Python's `last_error`.\n #lastError: SynapError | null = null;\n // Disk backing for the two buffers above. They survive a dropped\n // connection and not the process; this is what the next start reads.\n readonly #journal: OutboxJournal;\n\n #grpc: typeof import('@grpc/grpc-js') | null = null;\n #ServiceStub: (new (target: string, creds: unknown, opts: unknown) => Record<string, unknown>) | null = null;\n\n readonly #credentials: StreamCredentials;\n readonly #cache: AnticipationCache;\n readonly #options: StreamClientOptions;\n readonly #sleep: (ms: number) => Promise<void>;\n readonly #connectTimeoutMs: number;\n\n constructor(\n credentials: StreamCredentials,\n cache: AnticipationCache,\n options: StreamClientOptions = {},\n ) {\n this.#credentials = credentials;\n this.#cache = cache;\n this.#options = options;\n this.host = options.host ?? 'synap-cloud-prod.maximem.ai';\n this.port = options.port ?? 443;\n this.useTls = options.useTls ?? true;\n this.#sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));\n this.#connectTimeoutMs = options.connectTimeoutMs ?? 10_000;\n this.#journal = new OutboxJournal(\n options.outboxPath ?? defaultOutboxRoot(), credentials.instanceId);\n }\n\n get currentState(): StreamState { return this.#state; }\n get isConnected(): boolean { return this.#state === 'connected'; }\n\n /**\n * The error that ended the stream, if one did.\n *\n * Set when the server refused the stream for credits, so an `onDisconnect`\n * handler can read the balance, the reason and the URL to fix it. Null for an\n * ordinary disconnect.\n */\n get lastError(): SynapError | null { return this.#lastError; }\n\n async connect(): Promise<void> {\n await this.#loadGrpc();\n // Resolve node:fs once, before anything can be written. Every journal\n // append after this is synchronous, which is the point: an async write\n // still pending when the process dies is a write that did not happen.\n await OutboxJournal.init();\n this.#shutdownRequested = false;\n this.#lastError = null;\n // Before the connection, so a previous process's unsent events are already\n // in the queue when the stream comes up and go out ahead of this process's\n // first event. That is the order they happened in.\n this.#restoreFromJournal();\n await this.#establish();\n this.#startHeartbeat();\n }\n\n /**\n * Pull a previous process's unsent events back into the send queue.\n *\n * Everything lands in the send queue, including what was unacknowledged last\n * time. The stream those events were written to is gone, so \"the server may\n * already have it\" is not a state we can resume; it has to be written again.\n * That is safe because each event carries the id it was first sent with and\n * the server drops a second sighting of it before dispatch, so an event it\n * DID process is deduplicated there rather than doubling the turn.\n */\n #restoreFromJournal(): void {\n const { queued, unacked } = this.#journal.load();\n if (queued.length === 0 && unacked.length === 0) return;\n const now = Date.now();\n // Unacknowledged first: an event written to the old stream is older than\n // anything that piled up after it died.\n for (const event of [...unacked, ...queued]) {\n this.#sendQueue.push({\n queuedAt: now, event: event as unknown as ConversationEventInput,\n });\n }\n console.warn(\n `[synap] restored ${unacked.length + queued.length} event(s) from the `\n + `previous process (${unacked.length} unacknowledged, ${queued.length} `\n + 'queued); they go out on this stream',\n );\n this.#compactJournal();\n }\n\n #compactJournal(): void {\n this.#journal.compact(\n this.#sendQueue.map((h) => h.event as unknown as Record<string, unknown>),\n [...this.#unacked.values()].map(\n (h) => h.event as unknown as Record<string, unknown>),\n );\n }\n\n async disconnect(): Promise<void> {\n // Stop the heartbeat first: it writes, and a write after the half-close\n // below is an error.\n this.#shutdownRequested = true;\n this.#stopHeartbeat();\n\n // What is still buffered goes out before the half-close.\n this.#flushBeforeClose();\n\n // ⚠ `end()` half-closes the write side, which is right, but on its own it\n // is not enough. It used to return immediately and the process would exit\n // while the server was still reading, so the events were written and never\n // processed. Waiting for the server to finish its side is what makes the\n // half-close mean anything.\n //\n // Bounded, because a shutdown that hangs on a dead network is its own kind\n // of broken, and whatever misses the deadline is reported rather than lost\n // silently. See `undelivered()`.\n const call = this.#call;\n if (call !== null) {\n try { call.end(); } catch { /* already torn down */ }\n await new Promise<void>((resolve) => {\n let settled = false;\n const done = () => { if (!settled) { settled = true; resolve(); } };\n const timer = setTimeout(() => {\n const left = this.undelivered();\n if (left.unacknowledged > 0 || left.queued > 0) {\n console.warn(\n `[synap] closing with ${left.queued} queued and `\n + `${left.unacknowledged} unacknowledged event(s) the server did `\n + `not confirm within ${CLOSE_GRACE_MS}ms`,\n );\n }\n done();\n }, CLOSE_GRACE_MS);\n if (typeof timer.unref === 'function') timer.unref();\n call.on('end', () => { clearTimeout(timer); done(); });\n call.on('status', () => { clearTimeout(timer); done(); });\n call.on('error', () => { clearTimeout(timer); done(); });\n });\n }\n\n this.#call = null;\n // After the flush and the half-close, so the file describes what is\n // actually still outstanding. An empty pair removes it, which is how a\n // clean shutdown leaves nothing for the next start to replay.\n this.#compactJournal();\n this.#setState('closed');\n }\n\n /**\n * What this client could not get to the server.\n *\n * Every send path returns void, so a caller had no way to tell a delivered\n * turn from a lost one. Mirrors Python's `undelivered()`.\n */\n undelivered(): { queued: number; unacknowledged: number } {\n return { queued: this.#sendQueue.length, unacknowledged: this.#unacked.size };\n }\n\n /**\n * Never fails the caller's turn, and no longer loses it either.\n *\n * A write that cannot go out is buffered and replayed on reconnect, and a\n * write that does go out is held until the server acknowledges it. Before\n * this, both cases ended at a silent `return`.\n */\n sendConversationEvent(event: ConversationEventInput): void {\n const stamped: ConversationEventInput = {\n ...event,\n event_id: event.event_id && event.event_id !== '' ? event.event_id : newCorrelationId(),\n sent_at_ms: event.sent_at_ms ?? Date.now(),\n };\n if (!this.isConnected || this.#call === null) {\n this.#enqueue(stamped);\n return;\n }\n this.#write({ conversation_event: { ...stamped } });\n this.#trackUnacked(stamped);\n }\n\n #enqueue(event: ConversationEventInput): void {\n this.#pruneQueue();\n if (this.#sendQueue.length >= SEND_QUEUE_MAX_DEPTH) {\n const evicted = this.#sendQueue.shift();\n this.#journal.recordDropped(evicted?.event.event_id);\n console.warn(\n `[synap] send queue full (max=${SEND_QUEUE_MAX_DEPTH}); dropping oldest `\n + `event (event_type=${evicted?.event.event_type})`,\n );\n }\n this.#sendQueue.push({ queuedAt: Date.now(), event });\n this.#journal.recordQueued(event as unknown as Record<string, unknown>);\n }\n\n #pruneQueue(): void {\n const cutoff = Date.now() - SEND_QUEUE_MAX_AGE_MS;\n while (this.#sendQueue.length > 0 && (this.#sendQueue[0]?.queuedAt ?? 0) < cutoff) {\n const expired = this.#sendQueue.shift();\n this.#journal.recordDropped(expired?.event.event_id);\n console.warn(\n `[synap] queued event expired (age>${SEND_QUEUE_MAX_AGE_MS}ms); dropping `\n + `(event_type=${expired?.event.event_type})`,\n );\n }\n }\n\n #trackUnacked(event: ConversationEventInput): void {\n const id = event.event_id;\n if (id === undefined || id === '') return;\n const cutoff = Date.now() - SEND_QUEUE_MAX_AGE_MS;\n for (const [oldestId, held] of this.#unacked) {\n if (held.queuedAt >= cutoff && this.#unacked.size < SEND_QUEUE_MAX_DEPTH) break;\n this.#unacked.delete(oldestId);\n this.#journal.recordDropped(oldestId);\n console.warn(\n '[synap] unacknowledged event dropped: the server never confirmed it '\n + `(event_type=${held.event.event_type})`,\n );\n }\n this.#unacked.set(id, { queuedAt: Date.now(), event });\n this.#journal.recordUnacked(event as unknown as Record<string, unknown>);\n }\n\n /**\n * Replay what the server never confirmed, then what never went out.\n *\n * That order is the order things happened in: an unacked event was written\n * to the previous stream, so it is older than anything that piled up while\n * there was no stream at all. Safe to repeat — every event carries the id it\n * was first sent with and the server drops a second sighting of it.\n */\n #replayBuffered(): void {\n const unacked = [...this.#unacked.values()];\n const queued = this.#sendQueue;\n this.#sendQueue = [];\n for (const held of [...unacked, ...queued]) {\n this.#write({ conversation_event: { ...held.event } });\n }\n if (unacked.length > 0 || queued.length > 0) {\n console.warn(\n `[synap] replayed ${unacked.length} unacknowledged and ${queued.length} `\n + 'queued event(s) after reconnect',\n );\n }\n }\n\n sendSessionControl(input: {\n action: string;\n session_id?: string;\n conversation_id?: string;\n user_id?: string;\n customer_id?: string;\n }): void {\n this.#write({ session_control: { ...input } });\n }\n\n sendContextUsed(event: Record<string, unknown>): void {\n this.#write({ context_used: { ...event } });\n }\n\n sendContextAssembled(event: Record<string, unknown>): void {\n this.#write({ context_assembled: { ...event } });\n }\n\n #flushBeforeClose(): void {\n if (!this.isConnected || this.#call === null) {\n if (this.#sendQueue.length > 0 || this.#unacked.size > 0) {\n console.warn(\n `[synap] closing with ${this.#sendQueue.length} queued and `\n + `${this.#unacked.size} unacknowledged event(s) and no stream to send `\n + 'them on; they are lost',\n );\n }\n return;\n }\n const queued = this.#sendQueue;\n this.#sendQueue = [];\n for (const held of queued) {\n this.#write({ conversation_event: { ...held.event } });\n }\n }\n\n #write(msg: unknown): void {\n if (!this.isConnected || this.#call === null) return;\n try {\n this.#call.write(msg);\n } catch {\n // Non-fatal. These are telemetry and control events; losing one is\n // strictly better than surfacing a stream hiccup to the caller.\n }\n }\n\n async #loadGrpc(): Promise<void> {\n if (this.#grpc !== null) return;\n const [grpcMod, protoMod] = await Promise.all([\n import(/* @vite-ignore */ '@grpc/grpc-js'),\n import(/* @vite-ignore */ '@grpc/proto-loader'),\n ]);\n this.#grpc = grpcMod;\n\n // `fromJSON`, not `loadSync`: no path, no disk read, nothing for a bundler\n // to fail to trace. `keepCase` keeps the snake_case field names the server\n // and the Python SDK both use.\n const pkgDef = protoMod.fromJSON(\n SYNAP_PROTO_DESCRIPTOR as unknown as Parameters<typeof protoMod.fromJSON>[0],\n { keepCase: true, longs: Number, enums: String, defaults: true, oneofs: true },\n );\n const pkg = grpcMod.loadPackageDefinition(pkgDef) as unknown as {\n synap: { v1: { SynapService: new (t: string, c: unknown, o: unknown) => Record<string, unknown> } };\n };\n this.#ServiceStub = pkg.synap.v1.SynapService;\n }\n\n async #establish(): Promise<void> {\n this.#setState('connecting');\n const grpc = this.#grpc;\n const Stub = this.#ServiceStub;\n if (grpc === null || Stub === null) throw new Error('gRPC not loaded');\n\n const channelCreds = this.useTls\n ? grpc.credentials.createSsl()\n : grpc.credentials.createInsecure();\n\n const stub = new Stub(`${this.host}:${this.port}`, channelCreds, {\n 'grpc.keepalive_time_ms': 30_000,\n 'grpc.keepalive_timeout_ms': 10_000,\n 'grpc.keepalive_permit_without_calls': 1,\n });\n\n // Wait for the channel before reporting success.\n //\n // grpc-js connects LAZILY: constructing the stub and creating the call both\n // return immediately, and a bad host surfaces later through the call's\n // 'error' event. Without this, `await listen()` resolved against an\n // unreachable port, `is_listening` read true for a stream that never\n // connected, and `send_message()` then dropped every event silently,\n // because sends are deliberately fire-and-forget. Failing loudly here is\n // the difference between a clear error and an agent that looks like it has\n // no memory.\n await new Promise<void>((resolve, reject) => {\n const deadline = new Date(Date.now() + this.#connectTimeoutMs);\n const waitForReady = (stub as unknown as {\n waitForReady?: (d: Date, cb: (e?: Error) => void) => void;\n }).waitForReady;\n if (typeof waitForReady !== 'function') {\n // Older grpc-js, or a stub that does not expose it. Fall back to the\n // lazy behaviour rather than failing outright.\n resolve();\n return;\n }\n waitForReady.call(stub, deadline, (error?: Error) => {\n if (error) reject(error);\n else resolve();\n });\n });\n\n const metadata = new grpc.Metadata();\n metadata.add('authorization', `Bearer ${this.#credentials.apiKey}`);\n metadata.add('x-client-id', this.#credentials.clientId);\n metadata.add('x-instance-id', this.#credentials.instanceId);\n\n const call = (stub['Listen'] as (m: unknown) => DuplexCall)(metadata);\n call.on('data', (msg) => this.#handleMessage(msg as Record<string, unknown>));\n call.on('error', (err) => {\n // A credit refusal arrives here, not from waitForReady: the channel is\n // healthy and the gate aborts the call itself.\n const creditError = creditErrorFromRpc(err);\n if (creditError !== null) {\n this.#handleCreditStop(creditError);\n return;\n }\n void this.#handleDisconnect(`error: ${String(err)}`);\n });\n call.on('end', () => { void this.#handleDisconnect('server_close'); });\n\n // NOTE, and this is where JS and Python legitimately differ.\n //\n // Python waits on `call.wait_for_connection()` here, because grpc.aio\n // reports CONNECTED off a call object the server has not accepted and\n // events written in that window were written into a buffer that the close\n // then discarded.\n //\n // grpc-js has no equivalent, and the obvious stand-in does not work: a\n // grpc-js server sends no initial metadata until it writes, so waiting on\n // the 'metadata' event stalls for the whole connect timeout on a server\n // that is perfectly healthy. Measured: it made every test here time out.\n //\n // It is also not needed. grpc-js buffers writes on the call and flushes\n // them once the call is established, so an event written this early is not\n // lost by the write. It is lost by the TEARDOWN, which is what\n // `disconnect()` now fixes: half-close, then wait for the server to finish\n // reading. The parity is in the outcome the tests assert, a whole turn\n // reaching the server with no pause anywhere, not in the mechanism.\n this.#call = call;\n this.#setState('connected');\n this.#reconnectAttempts = 0;\n this.#lastPongAt = Date.now();\n // Before anything else on the new stream: the turns that happened while\n // there was not one.\n this.#replayBuffered();\n }\n\n #handleMessage(msg: Record<string, unknown>): void {\n const ack = msg['event_ack'] as { event_ids?: string[] } | undefined;\n if (ack) {\n for (const id of ack.event_ids ?? []) {\n this.#unacked.delete(id);\n this.#journal.recordAcked(id);\n }\n if (this.#journal.needsCompaction) this.#compactJournal();\n return;\n }\n if (msg['context_bundle']) {\n this.#handleBundle(msg['context_bundle'] as Record<string, unknown>);\n return;\n }\n if (msg['heartbeat_pong']) {\n this.#lastPongAt = Date.now();\n this.#missedHeartbeats = 0;\n return;\n }\n const signal = msg['signal'] as { signal_type?: string } | undefined;\n if (signal?.signal_type === 'closing') {\n void this.#handleDisconnect('server_signal_closing');\n }\n }\n\n #handleBundle(bundle: Record<string, unknown>): void {\n const bundleType = String(bundle['bundle_type'] ?? '') || 'anticipation';\n const conversationId = String(bundle['anticipation_conversation_id'] ?? '');\n\n if (bundleType !== UNCACHEABLE_BUNDLE_TYPE) this.#store(bundle, bundleType, conversationId);\n\n if (bundleType === 'compaction_update' && conversationId !== '') {\n // Python also deletes this conversation's `compacted_full` /\n // `compacted_context` entries from its separate CacheManager. This SDK\n // has no such cache, and calling dropConversation() here would delete\n // the bundle just stored above, so there is nothing to invalidate.\n this.#options.onCompactionUpdate?.(conversationId, bundle);\n }\n\n // Fires for every type, after storing, matching Python's ordering.\n this.#options.onContext?.(bundle);\n }\n\n #store(bundle: Record<string, unknown>, bundleType: string, conversationId: string): void {\n const itemsByType: Record<string, Array<Record<string, unknown>>> = {};\n for (const [type, list] of Object.entries(bundle['items_by_type'] ?? {})) {\n const items = (list as { items?: Array<Record<string, unknown>> })?.items;\n itemsByType[type] = items ?? [];\n }\n\n const ttlHint = Number(bundle['ttl_hint_seconds'] ?? 0);\n this.#cache.store({\n bundleId: String(bundle['bundle_id'] ?? ''),\n // The cache keys on ONE entity. User id wins when present, so a bundle\n // pushed for a user cannot be served to a different user in the same\n // customer; falling back to customer id keeps org-scope bundles usable.\n entityId:\n String(bundle['anticipation_user_id'] ?? '') ||\n String(bundle['anticipation_customer_id'] ?? '') ||\n null,\n conversationId: conversationId || null,\n // The rung the server retrieved this bundle at. Absent from anything an\n // older server sent, and absent reads as \"named no rung\" rather than as\n // \"matches anything\".\n scopeRung: String(bundle['anticipation_scope_rung'] ?? '') || null,\n itemsByType,\n // 0 means \"use the SDK default\"; anything else is floored at 60s.\n ttlHintSeconds: ttlHint > 0 ? Math.max(ttlHint, MIN_TTL_SECONDS) : null,\n bundleType,\n searchQueries: [\n ...((bundle['search_queries'] as string[] | undefined) ?? []),\n ...((bundle['search_keywords'] as string[] | undefined) ?? []),\n ],\n });\n }\n\n /**\n * End the stream because the server refused it for credits.\n *\n * Reconnecting cannot clear a credit refusal, so the stream closes and the\n * typed error is kept on `lastError` for the application to read. Mirrors\n * Python's `_handle_credit_stop`.\n */\n #handleCreditStop(error: SynapError): void {\n this.#lastError = error;\n this.#shutdownRequested = true;\n this.#stopHeartbeat();\n this.#closeCall();\n this.#setState('disconnected');\n }\n\n async #handleDisconnect(_reason: string): Promise<void> {\n if (this.#shutdownRequested) return;\n\n this.#closeCall();\n this.#setState('reconnecting');\n\n if (this.#reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {\n this.#setState('disconnected');\n return;\n }\n\n // Jittered exponential backoff. The jitter matters with many SDK instances\n // behind one deploy: without it they all retry in lockstep.\n const delay = Math.min(\n BACKOFF_BASE_MS * 2 ** this.#reconnectAttempts + Math.random() * 1_000,\n BACKOFF_MAX_MS,\n );\n this.#reconnectAttempts += 1;\n\n await this.#sleep(delay);\n if (this.#shutdownRequested) return;\n try {\n await this.#establish();\n } catch (error) {\n if (isCreditStop(error)) {\n this.#handleCreditStop(error as SynapError);\n return;\n }\n void this.#handleDisconnect('reconnect_failed');\n }\n }\n\n #startHeartbeat(): void {\n this.#heartbeatTimer = setInterval(() => {\n if (!this.isConnected || this.#call === null) return;\n const now = Date.now();\n\n if (now - this.#lastPongAt > HEARTBEAT_TIMEOUT_MS) {\n this.#missedHeartbeats += 1;\n if (this.#missedHeartbeats >= MAX_MISSED_HEARTBEATS) {\n void this.#handleDisconnect('heartbeat_timeout');\n return;\n }\n }\n try {\n this.#call.write({ heartbeat_ping: { timestamp_ms: now } });\n } catch {\n void this.#handleDisconnect('heartbeat_write_error');\n }\n }, HEARTBEAT_INTERVAL_MS);\n // Never hold the process open on the heartbeat alone: a CLI that finishes\n // its work should exit, not hang for 30s.\n this.#heartbeatTimer.unref?.();\n }\n\n #stopHeartbeat(): void {\n if (this.#heartbeatTimer !== null) {\n clearInterval(this.#heartbeatTimer);\n this.#heartbeatTimer = null;\n }\n }\n\n #closeCall(): void {\n try {\n this.#call?.end();\n } catch {\n /* already torn down */\n }\n this.#call = null;\n }\n\n #setState(state: StreamState): void {\n this.#state = state;\n this.#options.onStateChange?.(state);\n }\n}\n"]}
|