@remnic/core 9.69.26 → 9.69.28
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/access-admin-ops-surface.d.ts +1 -1
- package/dist/access-admin-ops-surface.js +21 -21
- package/dist/access-authorization-probe.d.ts +1 -1
- package/dist/access-authorization-probe.js +22 -22
- package/dist/access-boundary.d.ts +1 -1
- package/dist/access-boundary.js +21 -21
- package/dist/access-cli.js +73 -73
- package/dist/access-extraction-force-flush.d.ts +1 -1
- package/dist/access-extraction-force-flush.js +21 -21
- package/dist/access-http-briefing.d.ts +1 -1
- package/dist/access-http-briefing.js +22 -22
- package/dist/access-http-lcm-compaction.d.ts +1 -1
- package/dist/access-http-lifecycle-flush.d.ts +1 -1
- package/dist/access-http-offline-stream.d.ts +1 -1
- package/dist/access-http-query.js +23 -23
- package/dist/access-http.d.ts +2 -2
- package/dist/access-http.js +38 -38
- package/dist/access-identity-continuity-surface.js +21 -21
- package/dist/access-lcm-surface.d.ts +1 -1
- package/dist/access-lcm-surface.js +21 -21
- package/dist/access-mcp.d.ts +1 -1
- package/dist/access-mcp.js +29 -29
- package/dist/access-namespace-preflight.js +21 -21
- package/dist/access-observe-write-surface.d.ts +1 -1
- package/dist/access-observe-write-surface.js +21 -21
- package/dist/access-operations-batch.js +23 -23
- package/dist/access-operations.d.ts +1 -1
- package/dist/access-operations.js +28 -28
- package/dist/access-recall-concurrency.d.ts +1 -1
- package/dist/access-recall-concurrency.js +21 -21
- package/dist/access-recall-response.d.ts +1 -1
- package/dist/access-recall-response.js +21 -21
- package/dist/access-recall-surface.d.ts +1 -1
- package/dist/access-recall-surface.js +21 -21
- package/dist/access-schema.js +3 -3
- package/dist/access-service-helpers.d.ts +1 -1
- package/dist/access-service.d.ts +1 -1
- package/dist/access-service.js +21 -21
- package/dist/access-surface-catalog.d.ts +1 -1
- package/dist/access-surface-catalog.js +23 -23
- package/dist/briefing.js +8 -8
- package/dist/buffer.js +4 -4
- package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-7FJQINUR.js} +2 -2
- package/dist/causal-consolidation.js +9 -9
- package/dist/{chunk-N3ZOP7KC.js → chunk-2AMEUKCG.js} +2 -2
- package/dist/{chunk-BXLOS5AJ.js → chunk-2NLLXCJG.js} +2 -2
- package/dist/{chunk-MWN6QEI2.js → chunk-2YDWI2LO.js} +2 -2
- package/dist/{chunk-6XL73HQ4.js → chunk-3FEH4C4V.js} +1 -1
- package/dist/{chunk-ZUNNRGJX.js → chunk-4KMGFWQY.js} +7 -7
- package/dist/{chunk-R7RTXEYG.js → chunk-4P43SE4H.js} +2 -2
- package/dist/{chunk-U6N5IBVY.js → chunk-5ISVONB7.js} +2 -2
- package/dist/{chunk-OEYZXDQI.js → chunk-5KND6CSX.js} +2 -2
- package/dist/{chunk-AQ7SYDVO.js → chunk-5PTRLTK2.js} +2 -2
- package/dist/{chunk-FHYVNJBN.js → chunk-6TZWGQTT.js} +2 -2
- package/dist/{chunk-VDX2J7OX.js → chunk-7LCGFUMB.js} +4 -4
- package/dist/{chunk-227UFSEQ.js → chunk-7RZJWZ46.js} +5 -5
- package/dist/{chunk-GHTSNGBZ.js → chunk-ALBYEXFX.js} +2 -2
- package/dist/{chunk-Y2MZGBOY.js → chunk-BHHONFSK.js} +2 -2
- package/dist/{chunk-ZO2AZIRT.js → chunk-BIVJCVT2.js} +2 -2
- package/dist/{chunk-CEH2AFXG.js → chunk-C24UZSFW.js} +6 -6
- package/dist/{chunk-MZLV2OJE.js → chunk-C6XEXTSJ.js} +34 -34
- package/dist/{chunk-QBH7M7Q4.js → chunk-COUYFFID.js} +2 -2
- package/dist/{chunk-V4ZBR7CW.js → chunk-EIJR4PZL.js} +3 -3
- package/dist/{chunk-TVEONGBU.js → chunk-EZKSOM5K.js} +2 -2
- package/dist/{chunk-SJDQ5RSN.js → chunk-GC65K6DI.js} +3 -3
- package/dist/{chunk-6NXSYYGP.js → chunk-HLTAZTN7.js} +2 -2
- package/dist/{chunk-E2P7TZTF.js → chunk-HTPQJ32S.js} +2 -2
- package/dist/{chunk-MY5EDR46.js → chunk-I3OQBP23.js} +2 -2
- package/dist/{chunk-QOOBICJF.js → chunk-IE57QQSZ.js} +2 -2
- package/dist/{chunk-ZY4DSWZ5.js → chunk-IES4QHLM.js} +4 -4
- package/dist/{chunk-2EKJDQZB.js → chunk-INBOGQ3E.js} +2 -2
- package/dist/{chunk-3GGUMRS4.js → chunk-MSSJSC2V.js} +2 -2
- package/dist/{chunk-KH6PK4EQ.js → chunk-MYB6DWE5.js} +6 -6
- package/dist/{chunk-WJHGDE7X.js → chunk-NC7RM2XS.js} +2 -2
- package/dist/{chunk-W24T5YMW.js → chunk-NOPN47AL.js} +2 -2
- package/dist/{chunk-RX3SC4C5.js → chunk-NVOFMARR.js} +2 -2
- package/dist/{chunk-JJ4XK25O.js → chunk-OMLGLMWL.js} +7 -7
- package/dist/{chunk-46ZPIGWI.js → chunk-OVAJKCN3.js} +2 -2
- package/dist/{chunk-EOQ6FSQP.js → chunk-OYHGTHVX.js} +2 -2
- package/dist/{chunk-5ATKSPEQ.js → chunk-PKS56ZRR.js} +2 -2
- package/dist/{chunk-6LLLREDJ.js → chunk-QW3FMKT2.js} +2 -2
- package/dist/{chunk-OU4K7IFP.js → chunk-R55WAWS3.js} +12 -12
- package/dist/{chunk-SO4IYSGT.js → chunk-R5T5FB2R.js} +40 -40
- package/dist/{chunk-6CVNSDCZ.js → chunk-RFWEDCRC.js} +3 -3
- package/dist/{chunk-FQ7HAWWC.js → chunk-RZN4LQ5Y.js} +17 -2
- package/dist/{chunk-FQ7HAWWC.js.map → chunk-RZN4LQ5Y.js.map} +1 -1
- package/dist/{chunk-GNMELJGM.js → chunk-S2DU3UBN.js} +4 -4
- package/dist/{chunk-VVX5PZFS.js → chunk-TDYAIXHK.js} +5 -5
- package/dist/{chunk-KAXCYM66.js → chunk-U6S754L6.js} +2 -2
- package/dist/{chunk-XM24CDZX.js → chunk-UEEN7GXR.js} +2 -2
- package/dist/{chunk-XSGUMQQQ.js → chunk-UY2RORVF.js} +2 -2
- package/dist/{chunk-THQTFTQS.js → chunk-UY7VPC6Q.js} +15 -15
- package/dist/{chunk-3NYTZE5G.js → chunk-W7OU4I3Y.js} +2 -2
- package/dist/{chunk-6VF3R3XQ.js → chunk-WFZBRS6N.js} +2 -2
- package/dist/{chunk-RBCQVOVJ.js → chunk-WOOTZ6DH.js} +3 -3
- package/dist/{chunk-DDB3S53L.js → chunk-WW3MR6T3.js} +2 -2
- package/dist/{chunk-2L5FSNPC.js → chunk-X2NFFBTO.js} +7 -7
- package/dist/{chunk-DQEMWVMT.js → chunk-X7Y7WX73.js} +1 -1
- package/dist/{chunk-XTJC7CVX.js → chunk-XR67IAXP.js} +2 -2
- package/dist/{chunk-5IEV3YBY.js → chunk-YFDJVB2K.js} +2 -2
- package/dist/{chunk-4TKJGOCJ.js → chunk-YGTSOVCR.js} +2 -2
- package/dist/{chunk-UCKYTA2T.js → chunk-YIHKAUS4.js} +3 -3
- package/dist/{chunk-WMLEPWMO.js → chunk-YJETZFLJ.js} +2 -2
- package/dist/{chunk-CGJV2YIY.js → chunk-Z5XE7AMC.js} +2 -2
- package/dist/{chunk-2Z4GS5E6.js → chunk-ZMNT26KV.js} +2 -2
- package/dist/{chunk-JEC4DVM3.js → chunk-ZNR5SVUI.js} +3 -3
- package/dist/{cli-B_YwqoIr.d.ts → cli-BqMNkQn5.d.ts} +1 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +52 -52
- package/dist/coding/export-okf-codegraph.js +8 -8
- package/dist/compounding/engine.js +8 -8
- package/dist/connectors/codex-materialize-runner.js +8 -8
- package/dist/connectors/index.js +8 -8
- package/dist/convergence-refresh.js +10 -10
- package/dist/conversation-index/backend.js +2 -2
- package/dist/corpus-watermark.js +10 -10
- package/dist/deep-recall-http-glue.d.ts +1 -1
- package/dist/deep-recall-http-glue.js +4 -4
- package/dist/deep-recall-mcp-tools.d.ts +1 -1
- package/dist/entity-retrieval.js +8 -8
- package/dist/external-wiki-access.d.ts +1 -1
- package/dist/external-wiki-access.js +22 -22
- package/dist/external-wiki-mcp-tools.d.ts +1 -1
- package/dist/extraction.js +11 -11
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/http-retry.d.ts +83 -0
- package/dist/http-retry.js +101 -0
- package/dist/http-retry.js.map +1 -0
- package/dist/importers/index.d.ts +21 -1
- package/dist/importers/index.js +3 -1
- package/dist/importers/test-utils.d.ts +24 -0
- package/dist/importers/test-utils.js +31 -0
- package/dist/importers/test-utils.js.map +1 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +93 -91
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.js +6 -6
- package/dist/location/index.js +10 -10
- package/dist/maintenance/memory-governance.js +8 -8
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +8 -8
- package/dist/maintenance/rebuild-memory-projection.js +9 -9
- package/dist/mcp-memory-inspector-app.d.ts +1 -1
- package/dist/memory-subject-cli.d.ts +1 -1
- package/dist/memory-subject.d.ts +1 -1
- package/dist/namespaces/migrate.js +9 -9
- package/dist/namespaces/search.js +6 -6
- package/dist/namespaces/storage.js +8 -8
- package/dist/operator-doctor-corpus.js +11 -11
- package/dist/operator-toolkit.js +16 -16
- package/dist/orchestration/maintenance.js +16 -16
- package/dist/orchestrator.js +74 -74
- package/dist/{public-http-D6d4W8x-.d.ts → public-http-ByX2eTla.d.ts} +1 -1
- package/dist/qmd.js +4 -4
- package/dist/schemas.d.ts +42 -42
- package/dist/search/factory.js +5 -5
- package/dist/search/index.js +5 -5
- package/dist/semantic-consolidation.js +9 -9
- package/dist/semantic-rule-promotion.js +8 -8
- package/dist/semantic-rule-verifier.js +8 -8
- package/dist/source-agent-qualifier.js +10 -10
- package/dist/standup/build.js +8 -8
- package/dist/storage.js +7 -7
- package/dist/support-passport/index.d.ts +4 -4
- package/dist/support-passport/index.js +24 -24
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/transfer/export-okf.js +9 -9
- package/dist/transfer/types.d.ts +12 -12
- package/dist/verified-recall.js +8 -8
- package/dist/who-knows-cli.d.ts +1 -1
- package/dist/who-knows-cli.js +9 -9
- package/dist/who-knows.d.ts +1 -1
- package/dist/who-knows.js +8 -8
- package/package.json +22 -2
- package/src/http-retry.test.ts +281 -0
- package/src/http-retry.ts +183 -0
- package/src/importers/base.ts +45 -0
- package/src/importers/index.test.ts +42 -0
- package/src/importers/index.ts +5 -0
- package/src/importers/test-utils.ts +44 -0
- package/src/index.ts +3 -0
- package/dist/{access-service-Cv8Rizo0.d.ts → access-service-GrdVg_-Z.d.ts} +6 -6
- /package/dist/{capsule-crypto-YO5QJ6L3.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
- /package/dist/{chunk-N3ZOP7KC.js.map → chunk-2AMEUKCG.js.map} +0 -0
- /package/dist/{chunk-BXLOS5AJ.js.map → chunk-2NLLXCJG.js.map} +0 -0
- /package/dist/{chunk-MWN6QEI2.js.map → chunk-2YDWI2LO.js.map} +0 -0
- /package/dist/{chunk-6XL73HQ4.js.map → chunk-3FEH4C4V.js.map} +0 -0
- /package/dist/{chunk-ZUNNRGJX.js.map → chunk-4KMGFWQY.js.map} +0 -0
- /package/dist/{chunk-R7RTXEYG.js.map → chunk-4P43SE4H.js.map} +0 -0
- /package/dist/{chunk-U6N5IBVY.js.map → chunk-5ISVONB7.js.map} +0 -0
- /package/dist/{chunk-OEYZXDQI.js.map → chunk-5KND6CSX.js.map} +0 -0
- /package/dist/{chunk-AQ7SYDVO.js.map → chunk-5PTRLTK2.js.map} +0 -0
- /package/dist/{chunk-FHYVNJBN.js.map → chunk-6TZWGQTT.js.map} +0 -0
- /package/dist/{chunk-VDX2J7OX.js.map → chunk-7LCGFUMB.js.map} +0 -0
- /package/dist/{chunk-227UFSEQ.js.map → chunk-7RZJWZ46.js.map} +0 -0
- /package/dist/{chunk-GHTSNGBZ.js.map → chunk-ALBYEXFX.js.map} +0 -0
- /package/dist/{chunk-Y2MZGBOY.js.map → chunk-BHHONFSK.js.map} +0 -0
- /package/dist/{chunk-ZO2AZIRT.js.map → chunk-BIVJCVT2.js.map} +0 -0
- /package/dist/{chunk-CEH2AFXG.js.map → chunk-C24UZSFW.js.map} +0 -0
- /package/dist/{chunk-MZLV2OJE.js.map → chunk-C6XEXTSJ.js.map} +0 -0
- /package/dist/{chunk-QBH7M7Q4.js.map → chunk-COUYFFID.js.map} +0 -0
- /package/dist/{chunk-V4ZBR7CW.js.map → chunk-EIJR4PZL.js.map} +0 -0
- /package/dist/{chunk-TVEONGBU.js.map → chunk-EZKSOM5K.js.map} +0 -0
- /package/dist/{chunk-SJDQ5RSN.js.map → chunk-GC65K6DI.js.map} +0 -0
- /package/dist/{chunk-6NXSYYGP.js.map → chunk-HLTAZTN7.js.map} +0 -0
- /package/dist/{chunk-E2P7TZTF.js.map → chunk-HTPQJ32S.js.map} +0 -0
- /package/dist/{chunk-MY5EDR46.js.map → chunk-I3OQBP23.js.map} +0 -0
- /package/dist/{chunk-QOOBICJF.js.map → chunk-IE57QQSZ.js.map} +0 -0
- /package/dist/{chunk-ZY4DSWZ5.js.map → chunk-IES4QHLM.js.map} +0 -0
- /package/dist/{chunk-2EKJDQZB.js.map → chunk-INBOGQ3E.js.map} +0 -0
- /package/dist/{chunk-3GGUMRS4.js.map → chunk-MSSJSC2V.js.map} +0 -0
- /package/dist/{chunk-KH6PK4EQ.js.map → chunk-MYB6DWE5.js.map} +0 -0
- /package/dist/{chunk-WJHGDE7X.js.map → chunk-NC7RM2XS.js.map} +0 -0
- /package/dist/{chunk-W24T5YMW.js.map → chunk-NOPN47AL.js.map} +0 -0
- /package/dist/{chunk-RX3SC4C5.js.map → chunk-NVOFMARR.js.map} +0 -0
- /package/dist/{chunk-JJ4XK25O.js.map → chunk-OMLGLMWL.js.map} +0 -0
- /package/dist/{chunk-46ZPIGWI.js.map → chunk-OVAJKCN3.js.map} +0 -0
- /package/dist/{chunk-EOQ6FSQP.js.map → chunk-OYHGTHVX.js.map} +0 -0
- /package/dist/{chunk-5ATKSPEQ.js.map → chunk-PKS56ZRR.js.map} +0 -0
- /package/dist/{chunk-6LLLREDJ.js.map → chunk-QW3FMKT2.js.map} +0 -0
- /package/dist/{chunk-OU4K7IFP.js.map → chunk-R55WAWS3.js.map} +0 -0
- /package/dist/{chunk-SO4IYSGT.js.map → chunk-R5T5FB2R.js.map} +0 -0
- /package/dist/{chunk-6CVNSDCZ.js.map → chunk-RFWEDCRC.js.map} +0 -0
- /package/dist/{chunk-GNMELJGM.js.map → chunk-S2DU3UBN.js.map} +0 -0
- /package/dist/{chunk-VVX5PZFS.js.map → chunk-TDYAIXHK.js.map} +0 -0
- /package/dist/{chunk-KAXCYM66.js.map → chunk-U6S754L6.js.map} +0 -0
- /package/dist/{chunk-XM24CDZX.js.map → chunk-UEEN7GXR.js.map} +0 -0
- /package/dist/{chunk-XSGUMQQQ.js.map → chunk-UY2RORVF.js.map} +0 -0
- /package/dist/{chunk-THQTFTQS.js.map → chunk-UY7VPC6Q.js.map} +0 -0
- /package/dist/{chunk-3NYTZE5G.js.map → chunk-W7OU4I3Y.js.map} +0 -0
- /package/dist/{chunk-6VF3R3XQ.js.map → chunk-WFZBRS6N.js.map} +0 -0
- /package/dist/{chunk-RBCQVOVJ.js.map → chunk-WOOTZ6DH.js.map} +0 -0
- /package/dist/{chunk-DDB3S53L.js.map → chunk-WW3MR6T3.js.map} +0 -0
- /package/dist/{chunk-2L5FSNPC.js.map → chunk-X2NFFBTO.js.map} +0 -0
- /package/dist/{chunk-DQEMWVMT.js.map → chunk-X7Y7WX73.js.map} +0 -0
- /package/dist/{chunk-XTJC7CVX.js.map → chunk-XR67IAXP.js.map} +0 -0
- /package/dist/{chunk-5IEV3YBY.js.map → chunk-YFDJVB2K.js.map} +0 -0
- /package/dist/{chunk-4TKJGOCJ.js.map → chunk-YGTSOVCR.js.map} +0 -0
- /package/dist/{chunk-UCKYTA2T.js.map → chunk-YIHKAUS4.js.map} +0 -0
- /package/dist/{chunk-WMLEPWMO.js.map → chunk-YJETZFLJ.js.map} +0 -0
- /package/dist/{chunk-CGJV2YIY.js.map → chunk-Z5XE7AMC.js.map} +0 -0
- /package/dist/{chunk-2Z4GS5E6.js.map → chunk-ZMNT26KV.js.map} +0 -0
- /package/dist/{chunk-JEC4DVM3.js.map → chunk-ZNR5SVUI.js.map} +0 -0
package/dist/who-knows.js
CHANGED
|
@@ -9,13 +9,13 @@ import {
|
|
|
9
9
|
loadWhoKnowsEntities,
|
|
10
10
|
tokenizeTopic,
|
|
11
11
|
validateWhoKnowsInput
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-INBOGQ3E.js";
|
|
13
|
+
import "./chunk-UY2RORVF.js";
|
|
14
14
|
import "./chunk-UHVXE24Y.js";
|
|
15
15
|
import "./chunk-46WM6JX2.js";
|
|
16
16
|
import "./chunk-D5F7TKMH.js";
|
|
17
|
-
import "./chunk-
|
|
18
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-ZMNT26KV.js";
|
|
18
|
+
import "./chunk-RFWEDCRC.js";
|
|
19
19
|
import "./chunk-BD7JTBYP.js";
|
|
20
20
|
import "./chunk-OB3XMWX5.js";
|
|
21
21
|
import "./chunk-CYEPCZN5.js";
|
|
@@ -24,10 +24,10 @@ import "./chunk-YQNNHLCG.js";
|
|
|
24
24
|
import "./chunk-4O6WRBGE.js";
|
|
25
25
|
import "./chunk-6KTXE6A3.js";
|
|
26
26
|
import "./chunk-GUMZC2BO.js";
|
|
27
|
-
import "./chunk-
|
|
27
|
+
import "./chunk-7RZJWZ46.js";
|
|
28
|
+
import "./chunk-A7Q43EUZ.js";
|
|
28
29
|
import "./chunk-ET356KSB.js";
|
|
29
30
|
import "./chunk-72A4L5JJ.js";
|
|
30
|
-
import "./chunk-A7Q43EUZ.js";
|
|
31
31
|
import "./chunk-S2V7RUCL.js";
|
|
32
32
|
import "./chunk-SO7SV523.js";
|
|
33
33
|
import "./chunk-SCU65EZI.js";
|
|
@@ -49,10 +49,10 @@ import "./chunk-RBOJCG4T.js";
|
|
|
49
49
|
import "./chunk-R7QY5ZVB.js";
|
|
50
50
|
import "./chunk-5WITETWU.js";
|
|
51
51
|
import "./chunk-6CIAGZ6X.js";
|
|
52
|
-
import "./chunk-
|
|
52
|
+
import "./chunk-C24UZSFW.js";
|
|
53
53
|
import "./chunk-JXN77SGO.js";
|
|
54
|
-
import "./chunk-IUM3VSHI.js";
|
|
55
54
|
import "./chunk-ZLQ37WJX.js";
|
|
55
|
+
import "./chunk-IUM3VSHI.js";
|
|
56
56
|
import "./chunk-3QQTXYZ6.js";
|
|
57
57
|
import "./chunk-BT7NVCML.js";
|
|
58
58
|
import "./chunk-C64XATK7.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "9.69.
|
|
3
|
+
"version": "9.69.28",
|
|
4
4
|
"description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -31,6 +31,16 @@
|
|
|
31
31
|
"remnic-source": "./src/salvage-envelope.ts",
|
|
32
32
|
"import": "./dist/salvage-envelope.js"
|
|
33
33
|
},
|
|
34
|
+
"./http-retry": {
|
|
35
|
+
"types": "./dist/http-retry.d.ts",
|
|
36
|
+
"remnic-source": "./src/http-retry.ts",
|
|
37
|
+
"import": "./dist/http-retry.js"
|
|
38
|
+
},
|
|
39
|
+
"./http-retry.js": {
|
|
40
|
+
"types": "./dist/http-retry.d.ts",
|
|
41
|
+
"remnic-source": "./src/http-retry.ts",
|
|
42
|
+
"import": "./dist/http-retry.js"
|
|
43
|
+
},
|
|
34
44
|
"./abort-error.js": {
|
|
35
45
|
"types": "./dist/abort-error.d.ts",
|
|
36
46
|
"remnic-source": "./src/abort-error.ts",
|
|
@@ -1215,6 +1225,16 @@
|
|
|
1215
1225
|
"remnic-source": "./src/importers/index.ts",
|
|
1216
1226
|
"import": "./dist/importers/index.js"
|
|
1217
1227
|
},
|
|
1228
|
+
"./importers/test-utils": {
|
|
1229
|
+
"types": "./dist/importers/test-utils.d.ts",
|
|
1230
|
+
"remnic-source": "./src/importers/test-utils.ts",
|
|
1231
|
+
"import": "./dist/importers/test-utils.js"
|
|
1232
|
+
},
|
|
1233
|
+
"./importers/test-utils.js": {
|
|
1234
|
+
"types": "./dist/importers/test-utils.d.ts",
|
|
1235
|
+
"remnic-source": "./src/importers/test-utils.ts",
|
|
1236
|
+
"import": "./dist/importers/test-utils.js"
|
|
1237
|
+
},
|
|
1218
1238
|
"./intent": {
|
|
1219
1239
|
"types": "./dist/intent.d.ts",
|
|
1220
1240
|
"remnic-source": "./src/intent.ts",
|
|
@@ -3227,7 +3247,7 @@
|
|
|
3227
3247
|
"core"
|
|
3228
3248
|
],
|
|
3229
3249
|
"peerDependencies": {
|
|
3230
|
-
"@remnic/coding-graph": "^9.69.
|
|
3250
|
+
"@remnic/coding-graph": "^9.69.28"
|
|
3231
3251
|
},
|
|
3232
3252
|
"peerDependenciesMeta": {
|
|
3233
3253
|
"@remnic/coding-graph": {
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
ConnectorApiError,
|
|
6
|
+
describeNetworkError,
|
|
7
|
+
discardResponseBody,
|
|
8
|
+
retryAfterHeaderMs,
|
|
9
|
+
retryingFetch,
|
|
10
|
+
stripTrailingSlashes,
|
|
11
|
+
} from "./http-retry.js";
|
|
12
|
+
|
|
13
|
+
function jsonResponse(body: unknown, status = 200, headers: Record<string, string> = {}): Response {
|
|
14
|
+
return new Response(JSON.stringify(body), { status, headers });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function scriptFetch(script: Array<() => Response | Promise<Response>>): {
|
|
18
|
+
fetchImpl: typeof fetch;
|
|
19
|
+
calls: () => number;
|
|
20
|
+
} {
|
|
21
|
+
let calls = 0;
|
|
22
|
+
return {
|
|
23
|
+
fetchImpl: (async () => {
|
|
24
|
+
const step = script[Math.min(calls, script.length - 1)];
|
|
25
|
+
calls += 1;
|
|
26
|
+
return step();
|
|
27
|
+
}) as typeof fetch,
|
|
28
|
+
calls: () => calls,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test("retries 5xx with exponential backoff, then returns the success response", async () => {
|
|
33
|
+
const sleeps: number[] = [];
|
|
34
|
+
const { fetchImpl, calls } = scriptFetch([
|
|
35
|
+
() => jsonResponse({}, 502),
|
|
36
|
+
() => jsonResponse({}, 503),
|
|
37
|
+
() => jsonResponse({ ok: true }),
|
|
38
|
+
]);
|
|
39
|
+
const response = await retryingFetch("https://api.example.invalid/v1", {
|
|
40
|
+
init: { method: "GET" },
|
|
41
|
+
fetchImpl,
|
|
42
|
+
sleep: async (ms) => {
|
|
43
|
+
sleeps.push(ms);
|
|
44
|
+
},
|
|
45
|
+
networkError: () => new ConnectorApiError("network"),
|
|
46
|
+
retryableError: (r) => new ConnectorApiError(`responded ${r.status}`, r.status),
|
|
47
|
+
});
|
|
48
|
+
assert.equal(response.status, 200);
|
|
49
|
+
assert.deepEqual(await response.json(), { ok: true });
|
|
50
|
+
assert.equal(calls(), 3);
|
|
51
|
+
assert.deepEqual(sleeps, [1_000, 2_000]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("exhausted retries on 5xx throw the vendor retryable error", async () => {
|
|
55
|
+
const sleeps: number[] = [];
|
|
56
|
+
const { fetchImpl, calls } = scriptFetch([() => jsonResponse({}, 503)]);
|
|
57
|
+
await assert.rejects(
|
|
58
|
+
retryingFetch("https://api.example.invalid", {
|
|
59
|
+
fetchImpl,
|
|
60
|
+
sleep: async (ms) => {
|
|
61
|
+
sleeps.push(ms);
|
|
62
|
+
},
|
|
63
|
+
networkError: () => new ConnectorApiError("network"),
|
|
64
|
+
retryableError: (r) => new ConnectorApiError(`responded ${r.status}`, r.status),
|
|
65
|
+
}),
|
|
66
|
+
(err: unknown) => err instanceof ConnectorApiError && err.status === 503,
|
|
67
|
+
);
|
|
68
|
+
assert.equal(calls(), 4);
|
|
69
|
+
assert.deepEqual(sleeps, [1_000, 2_000, 4_000]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("exhausted network failures call networkError with attempt count and timeout flag", async () => {
|
|
73
|
+
const pathy = new Error("connect ECONNREFUSED /home/user/.cache/loader.js");
|
|
74
|
+
(pathy as NodeJS.ErrnoException).code = "ECONNREFUSED";
|
|
75
|
+
const { fetchImpl } = scriptFetch([
|
|
76
|
+
() => Promise.reject(pathy),
|
|
77
|
+
]);
|
|
78
|
+
let seen: { attempts: number; timedOut: boolean } | undefined;
|
|
79
|
+
await assert.rejects(
|
|
80
|
+
retryingFetch("https://api.example.invalid", {
|
|
81
|
+
fetchImpl,
|
|
82
|
+
sleep: async () => {},
|
|
83
|
+
networkError: (err, attempts, context) => {
|
|
84
|
+
seen = { attempts, ...context };
|
|
85
|
+
return new ConnectorApiError(`failed after ${attempts}: ${describeNetworkError(err)}`);
|
|
86
|
+
},
|
|
87
|
+
retryableError: (r) => new ConnectorApiError(`responded ${r.status}`, r.status),
|
|
88
|
+
}),
|
|
89
|
+
(err: unknown) =>
|
|
90
|
+
err instanceof ConnectorApiError && err.message === "failed after 4: Error (ECONNREFUSED)",
|
|
91
|
+
);
|
|
92
|
+
assert.deepEqual(seen, { attempts: 4, timedOut: false });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("a caller abort mid-flight rethrows the raw error without retrying", async () => {
|
|
96
|
+
const controller = new AbortController();
|
|
97
|
+
let calls = 0;
|
|
98
|
+
const { promise, reject } = Promise.withResolvers<never>();
|
|
99
|
+
const fetchImpl = (async (_url: string | URL | Request, init?: RequestInit) => {
|
|
100
|
+
calls += 1;
|
|
101
|
+
init?.signal?.addEventListener("abort", () =>
|
|
102
|
+
reject(new DOMException("This operation was aborted", "AbortError")),
|
|
103
|
+
);
|
|
104
|
+
return promise;
|
|
105
|
+
}) as typeof fetch;
|
|
106
|
+
const pending = retryingFetch("https://api.example.invalid", {
|
|
107
|
+
fetchImpl,
|
|
108
|
+
signal: controller.signal,
|
|
109
|
+
sleep: async () => {
|
|
110
|
+
throw new Error("must not sleep after a caller abort");
|
|
111
|
+
},
|
|
112
|
+
networkError: () => new ConnectorApiError("network"),
|
|
113
|
+
retryableError: (r) => new ConnectorApiError(`responded ${r.status}`, r.status),
|
|
114
|
+
});
|
|
115
|
+
const finished = assert.rejects(
|
|
116
|
+
pending,
|
|
117
|
+
(err: unknown) => err instanceof DOMException && err.name === "AbortError",
|
|
118
|
+
);
|
|
119
|
+
controller.abort();
|
|
120
|
+
await finished;
|
|
121
|
+
assert.equal(calls, 1);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("Retry-After seconds and HTTP-date override the exponential backoff, capped", async () => {
|
|
125
|
+
const sleeps: number[] = [];
|
|
126
|
+
const options = {
|
|
127
|
+
sleep: async (ms: number) => {
|
|
128
|
+
sleeps.push(ms);
|
|
129
|
+
},
|
|
130
|
+
networkError: () => new ConnectorApiError("network"),
|
|
131
|
+
retryableError: (r: Response) => new ConnectorApiError(`responded ${r.status}`, r.status),
|
|
132
|
+
};
|
|
133
|
+
{
|
|
134
|
+
const { fetchImpl } = scriptFetch([
|
|
135
|
+
() => jsonResponse({}, 429, { "retry-after": "7" }),
|
|
136
|
+
() => jsonResponse([]),
|
|
137
|
+
]);
|
|
138
|
+
await retryingFetch("https://api.example.invalid", { fetchImpl, ...options });
|
|
139
|
+
assert.deepEqual(sleeps, [7_000]);
|
|
140
|
+
}
|
|
141
|
+
{
|
|
142
|
+
sleeps.length = 0;
|
|
143
|
+
const { fetchImpl } = scriptFetch([
|
|
144
|
+
() => jsonResponse({}, 429, { "retry-after": "120" }),
|
|
145
|
+
() => jsonResponse([]),
|
|
146
|
+
]);
|
|
147
|
+
await retryingFetch("https://api.example.invalid", { fetchImpl, ...options });
|
|
148
|
+
assert.deepEqual(sleeps, [30_000], "hostile Retry-After is capped at maxRetryDelayMs");
|
|
149
|
+
}
|
|
150
|
+
{
|
|
151
|
+
sleeps.length = 0;
|
|
152
|
+
const soon = new Date(Date.now() + 3_000).toUTCString();
|
|
153
|
+
const { fetchImpl } = scriptFetch([
|
|
154
|
+
() => jsonResponse({}, 429, { "retry-after": soon }),
|
|
155
|
+
() => jsonResponse([]),
|
|
156
|
+
]);
|
|
157
|
+
await retryingFetch("https://api.example.invalid", { fetchImpl, ...options });
|
|
158
|
+
assert.ok(sleeps[0] !== undefined && sleeps[0] > 2_000 && sleeps[0] <= 3_000, `HTTP-date delay, got ${sleeps[0]}`);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("a vendor retryAfterMs override wins over the header; null falls back", async () => {
|
|
163
|
+
const sleeps: number[] = [];
|
|
164
|
+
const { fetchImpl } = scriptFetch([
|
|
165
|
+
() => jsonResponse({ retryAfter: "2" }, 429),
|
|
166
|
+
() => jsonResponse([]),
|
|
167
|
+
]);
|
|
168
|
+
await retryingFetch("https://api.example.invalid", {
|
|
169
|
+
fetchImpl,
|
|
170
|
+
sleep: async (ms) => {
|
|
171
|
+
sleeps.push(ms);
|
|
172
|
+
},
|
|
173
|
+
retryAfterMs: async (response) => {
|
|
174
|
+
const body = (await response.clone().json()) as { retryAfter?: unknown };
|
|
175
|
+
const parsed = Number(body?.retryAfter);
|
|
176
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed * 1_000 : null;
|
|
177
|
+
},
|
|
178
|
+
networkError: () => new ConnectorApiError("network"),
|
|
179
|
+
retryableError: (r) => new ConnectorApiError(`responded ${r.status}`, r.status),
|
|
180
|
+
});
|
|
181
|
+
assert.deepEqual(sleeps, [2_000]);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("non-retryable statuses are returned to the caller untouched", async () => {
|
|
185
|
+
const { fetchImpl, calls } = scriptFetch([() => jsonResponse({ error: "nope" }, 404)]);
|
|
186
|
+
const response = await retryingFetch("https://api.example.invalid", {
|
|
187
|
+
fetchImpl,
|
|
188
|
+
sleep: async () => {},
|
|
189
|
+
networkError: () => new ConnectorApiError("network"),
|
|
190
|
+
retryableError: (r) => new ConnectorApiError(`responded ${r.status}`, r.status),
|
|
191
|
+
});
|
|
192
|
+
assert.equal(response.status, 404);
|
|
193
|
+
assert.equal(calls(), 1);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("buildInit runs once; retries reuse the same init", async () => {
|
|
197
|
+
const tokens: string[] = [];
|
|
198
|
+
const { fetchImpl } = scriptFetch([
|
|
199
|
+
() => jsonResponse({}, 429),
|
|
200
|
+
() => jsonResponse([]),
|
|
201
|
+
]);
|
|
202
|
+
const seenAuth: (string | null)[] = [];
|
|
203
|
+
const fetchSpy = (async (url: string | URL | Request, init?: RequestInit) => {
|
|
204
|
+
seenAuth.push(new Headers(init?.headers).get("authorization"));
|
|
205
|
+
return fetchImpl(url, init);
|
|
206
|
+
}) as typeof fetch;
|
|
207
|
+
await retryingFetch("https://api.example.invalid", {
|
|
208
|
+
fetchImpl: fetchSpy,
|
|
209
|
+
sleep: async () => {},
|
|
210
|
+
buildInit: async () => {
|
|
211
|
+
const token = `tok-${tokens.length}`;
|
|
212
|
+
tokens.push(token);
|
|
213
|
+
return {
|
|
214
|
+
method: "POST",
|
|
215
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
216
|
+
body: "{}",
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
networkError: () => new ConnectorApiError("network"),
|
|
220
|
+
retryableError: (r) => new ConnectorApiError(`responded ${r.status}`, r.status),
|
|
221
|
+
});
|
|
222
|
+
assert.deepEqual(seenAuth, ["Bearer tok-0", "Bearer tok-0"]);
|
|
223
|
+
assert.equal(tokens.length, 1);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("buildInit failures run once, propagate unwrapped, and never fetch", async () => {
|
|
227
|
+
const failure = new ConnectorApiError("grant revoked: re-authorize");
|
|
228
|
+
let builds = 0;
|
|
229
|
+
const { fetchImpl, calls } = scriptFetch([]);
|
|
230
|
+
await assert.rejects(
|
|
231
|
+
retryingFetch("https://api.example.invalid", {
|
|
232
|
+
fetchImpl,
|
|
233
|
+
sleep: async () => {},
|
|
234
|
+
buildInit: async () => {
|
|
235
|
+
builds += 1;
|
|
236
|
+
throw failure;
|
|
237
|
+
},
|
|
238
|
+
networkError: (err, attempts) =>
|
|
239
|
+
new ConnectorApiError(`network after ${attempts}: ${String(err)}`),
|
|
240
|
+
retryableError: (r) => new ConnectorApiError(`responded ${r.status}`, r.status),
|
|
241
|
+
}),
|
|
242
|
+
(err: unknown) => err === failure,
|
|
243
|
+
);
|
|
244
|
+
assert.equal(builds, 1);
|
|
245
|
+
assert.equal(calls(), 0);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("backoffBaseMs, maxRetries, and maxRetryDelayMs are honored", async () => {
|
|
249
|
+
const sleeps: number[] = [];
|
|
250
|
+
const { fetchImpl } = scriptFetch([() => jsonResponse({}, 500)]);
|
|
251
|
+
await assert.rejects(
|
|
252
|
+
retryingFetch("https://api.example.invalid", {
|
|
253
|
+
fetchImpl,
|
|
254
|
+
sleep: async (ms) => {
|
|
255
|
+
sleeps.push(ms);
|
|
256
|
+
},
|
|
257
|
+
maxRetries: 2,
|
|
258
|
+
backoffBaseMs: 500,
|
|
259
|
+
maxRetryDelayMs: 8_000,
|
|
260
|
+
networkError: () => new ConnectorApiError("network"),
|
|
261
|
+
retryableError: (r) => new ConnectorApiError(`responded ${r.status}`, r.status),
|
|
262
|
+
}),
|
|
263
|
+
ConnectorApiError,
|
|
264
|
+
);
|
|
265
|
+
assert.deepEqual(sleeps, [500, 1_000]);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("retryAfterHeaderMs parses seconds and rejects garbage", () => {
|
|
269
|
+
assert.equal(retryAfterHeaderMs(jsonResponse({}, 429, { "retry-after": "1.5" }), 30_000), 1_500);
|
|
270
|
+
assert.equal(retryAfterHeaderMs(jsonResponse({}, 429, { "retry-after": "" }), 30_000), undefined);
|
|
271
|
+
assert.equal(retryAfterHeaderMs(jsonResponse({}, 429, { "retry-after": "soon" }), 30_000), undefined);
|
|
272
|
+
assert.equal(retryAfterHeaderMs(jsonResponse({}, 429), 30_000), undefined);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("stripTrailingSlashes and discardResponseBody behave as documented", async () => {
|
|
276
|
+
assert.equal(stripTrailingSlashes("https://api.example.invalid///"), "https://api.example.invalid");
|
|
277
|
+
assert.equal(stripTrailingSlashes("/"), "");
|
|
278
|
+
const response = jsonResponse({ b: 1 }, 429);
|
|
279
|
+
discardResponseBody(response);
|
|
280
|
+
await response.body?.cancel();
|
|
281
|
+
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared retrying fetch for connector packages (issue #2792).
|
|
3
|
+
*
|
|
4
|
+
* One copy of the retry loop that six connector clients and the X MCP
|
|
5
|
+
* client duplicated: attempt counting, per-attempt timeout signal,
|
|
6
|
+
* 429/5xx retry with Retry-After (seconds or HTTP-date) plus capped
|
|
7
|
+
* exponential backoff, raw rethrow of caller aborts, and socket
|
|
8
|
+
* release on retryable responses. Vendors keep everything
|
|
9
|
+
* request-specific: headers/body, the error classes their callers
|
|
10
|
+
* match on, and what happens to a successful response body.
|
|
11
|
+
*
|
|
12
|
+
* Import via the `@remnic/core/http-retry` subpath (same pattern as
|
|
13
|
+
* `@remnic/core/abort-error`) — the root index stays untouched.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface RetryingFetchOptions {
|
|
17
|
+
/** Request init for every attempt. Ignored when `buildInit` is set. */
|
|
18
|
+
init?: RequestInit;
|
|
19
|
+
/**
|
|
20
|
+
* Init builder invoked ONCE, before the retry loop. Request
|
|
21
|
+
* construction (e.g. token acquisition) must stay outside the
|
|
22
|
+
* transport retry boundary: a deterministic provider failure —
|
|
23
|
+
* missing grant, revoked grant, unpersistable token file — runs once
|
|
24
|
+
* and propagates unwrapped, never retried or vendor-wrapped.
|
|
25
|
+
*/
|
|
26
|
+
buildInit?: () => RequestInit | Promise<RequestInit>;
|
|
27
|
+
fetchImpl?: typeof fetch;
|
|
28
|
+
sleep?: (ms: number) => Promise<void>;
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
timeoutMs?: number;
|
|
31
|
+
/** Retries after the first attempt. Default 3 (4 attempts total). */
|
|
32
|
+
maxRetries?: number;
|
|
33
|
+
maxRetryDelayMs?: number;
|
|
34
|
+
/** First backoff step; doubles per attempt. Default 1_000. */
|
|
35
|
+
backoffBaseMs?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Vendor Retry-After parsing (e.g. a body field). A `null`/`undefined`
|
|
38
|
+
* result falls back to the shared Retry-After header parse, then to
|
|
39
|
+
* exponential backoff. A number (including 0) is used as-is.
|
|
40
|
+
*/
|
|
41
|
+
retryAfterMs?: (response: Response) => number | null | undefined | Promise<number | null | undefined>;
|
|
42
|
+
/** Wraps an exhausted network/timeout failure into the vendor error. */
|
|
43
|
+
networkError: (err: unknown, attempts: number, context: NetworkErrorContext) => Error;
|
|
44
|
+
/** Wraps a retryable (429/5xx) response; thrown when retries are exhausted. */
|
|
45
|
+
retryableError: (response: Response) => Error | Promise<Error>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface NetworkErrorContext {
|
|
49
|
+
/** True when the helper's own timeout signal fired (not a caller abort). */
|
|
50
|
+
timedOut: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Base class for connector API errors: a message plus optional HTTP
|
|
55
|
+
* status and vendor detail. Vendor subclasses set their own `name`.
|
|
56
|
+
*/
|
|
57
|
+
export class ConnectorApiError extends Error {
|
|
58
|
+
constructor(
|
|
59
|
+
message: string,
|
|
60
|
+
readonly status?: number,
|
|
61
|
+
readonly detail?: string,
|
|
62
|
+
) {
|
|
63
|
+
super(message);
|
|
64
|
+
this.name = "ConnectorApiError";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
69
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
70
|
+
const DEFAULT_MAX_RETRY_DELAY_MS = 30_000;
|
|
71
|
+
const DEFAULT_BACKOFF_BASE_MS = 1_000;
|
|
72
|
+
|
|
73
|
+
function defaultSleep(ms: number): Promise<void> {
|
|
74
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
75
|
+
setTimeout(resolve, ms);
|
|
76
|
+
return promise;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Network/timeout failures wrap Node error text that can carry loader
|
|
81
|
+
* paths or stack fragments; sync errors reach MCP clients verbatim, so
|
|
82
|
+
* only the error name + code survive.
|
|
83
|
+
*/
|
|
84
|
+
export function describeNetworkError(err: unknown): string {
|
|
85
|
+
if (!(err instanceof Error)) return "unexpected non-Error failure";
|
|
86
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
87
|
+
return typeof code === "string" && code.length > 0 ? `${err.name} (${code})` : err.name;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Loop instead of `/\/+$/` — CodeQL js/polynomial-redos on user-set URLs. */
|
|
91
|
+
export function stripTrailingSlashes(value: string): string {
|
|
92
|
+
let end = value.length;
|
|
93
|
+
while (end > 0 && value.charCodeAt(end - 1) === 0x2f) end--;
|
|
94
|
+
return value.slice(0, end);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Release the socket back to the pool: an unconsumed response body pins the undici connection. */
|
|
98
|
+
export function discardResponseBody(response: Response): void {
|
|
99
|
+
void response.body?.cancel().catch(() => {});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parse a `Retry-After` header as delay in ms, capped at `capMs`:
|
|
104
|
+
* delay-seconds first, then HTTP-date. `undefined` when absent or
|
|
105
|
+
* unparseable.
|
|
106
|
+
*/
|
|
107
|
+
export function retryAfterHeaderMs(response: Response, capMs: number): number | undefined {
|
|
108
|
+
const header = response.headers.get("retry-after");
|
|
109
|
+
if (header === null || header.length === 0) return undefined;
|
|
110
|
+
const seconds = Number(header);
|
|
111
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
112
|
+
return Math.min(capMs, Math.ceil(seconds * 1_000));
|
|
113
|
+
}
|
|
114
|
+
const when = Date.parse(header);
|
|
115
|
+
if (Number.isFinite(when)) {
|
|
116
|
+
return Math.min(capMs, Math.max(0, when - Date.now()));
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Fetch with retries on network failures, 429, and 5xx. Returns the
|
|
123
|
+
* final response (ok or non-retryable status) for the caller to
|
|
124
|
+
* interpret; throws the vendor-wrapped error when retries are exhausted
|
|
125
|
+
* or the transport fails.
|
|
126
|
+
*/
|
|
127
|
+
export async function retryingFetch(
|
|
128
|
+
url: string,
|
|
129
|
+
options: RetryingFetchOptions,
|
|
130
|
+
): Promise<Response> {
|
|
131
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
132
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
133
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
134
|
+
const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
135
|
+
const maxRetryDelayMs = options.maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;
|
|
136
|
+
const backoffBaseMs = options.backoffBaseMs ?? DEFAULT_BACKOFF_BASE_MS;
|
|
137
|
+
const backoffMs = (attempt: number): number =>
|
|
138
|
+
Math.min(maxRetryDelayMs, backoffBaseMs * 2 ** attempt);
|
|
139
|
+
const attempts = maxRetries + 1;
|
|
140
|
+
|
|
141
|
+
// Request construction runs once, outside the retry boundary: only
|
|
142
|
+
// fetch/network/HTTP-retryable failures retry, so initializer/token
|
|
143
|
+
// provider errors execute once and propagate unchanged (issue #2792).
|
|
144
|
+
options.signal?.throwIfAborted();
|
|
145
|
+
const init = options.buildInit ? await options.buildInit() : options.init;
|
|
146
|
+
|
|
147
|
+
let lastError: unknown;
|
|
148
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
149
|
+
options.signal?.throwIfAborted();
|
|
150
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
151
|
+
const combined = options.signal
|
|
152
|
+
? AbortSignal.any([options.signal, timeoutSignal])
|
|
153
|
+
: timeoutSignal;
|
|
154
|
+
let response: Response;
|
|
155
|
+
try {
|
|
156
|
+
response = await fetchImpl(url, { ...init, signal: combined });
|
|
157
|
+
} catch (err) {
|
|
158
|
+
// The caller's abort always propagates unwrapped and unretried.
|
|
159
|
+
if (options.signal?.aborted) throw err;
|
|
160
|
+
if (attempt < maxRetries) {
|
|
161
|
+
await sleep(backoffMs(attempt));
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
throw options.networkError(err, attempts, { timedOut: timeoutSignal.aborted });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (response.status === 429 || response.status >= 500) {
|
|
168
|
+
lastError = await options.retryableError(response);
|
|
169
|
+
if (attempt < maxRetries) {
|
|
170
|
+
const override = options.retryAfterMs ? await options.retryAfterMs(response) : undefined;
|
|
171
|
+
const delay = override ?? retryAfterHeaderMs(response, maxRetryDelayMs) ?? backoffMs(attempt);
|
|
172
|
+
discardResponseBody(response);
|
|
173
|
+
await sleep(delay);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
discardResponseBody(response);
|
|
177
|
+
throw lastError;
|
|
178
|
+
}
|
|
179
|
+
return response;
|
|
180
|
+
}
|
|
181
|
+
// Unreachable: every terminal loop path throws. Kept for exhaustiveness.
|
|
182
|
+
throw lastError instanceof Error ? lastError : new ConnectorApiError("request failed");
|
|
183
|
+
}
|
package/src/importers/base.ts
CHANGED
|
@@ -483,3 +483,48 @@ export async function defaultWriteMemoriesToOrchestrator(
|
|
|
483
483
|
await target.ingestBulkImportBatch(turns);
|
|
484
484
|
return { memoriesIngested: memories.length };
|
|
485
485
|
}
|
|
486
|
+
|
|
487
|
+
// ---------------------------------------------------------------------------
|
|
488
|
+
// File-importer factory (issue #2794)
|
|
489
|
+
// ---------------------------------------------------------------------------
|
|
490
|
+
|
|
491
|
+
/** Parse args `defineFileImporterAdapter` forwards — the keys file adapters use. */
|
|
492
|
+
export interface FileImporterParseArgs {
|
|
493
|
+
strict?: boolean;
|
|
494
|
+
filePath?: string;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/** Per-package config consumed by `defineFileImporterAdapter`. */
|
|
498
|
+
export interface FileImporterDefinition<Parsed> {
|
|
499
|
+
name: string;
|
|
500
|
+
sourceLabel: string;
|
|
501
|
+
/** Decode the raw export file. Receives only `strict` / `filePath`. */
|
|
502
|
+
parse(input: unknown, options: FileImporterParseArgs): Parsed | Promise<Parsed>;
|
|
503
|
+
/** Flatten the parsed export into `ImportedMemory[]` with provenance. */
|
|
504
|
+
transform(
|
|
505
|
+
parsed: Parsed,
|
|
506
|
+
options: ImporterTransformOptions | undefined,
|
|
507
|
+
): ImportedMemory[] | Promise<ImportedMemory[]>;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Build the standard file-importer `ImporterAdapter`: parse with the
|
|
512
|
+
* `strict`/`filePath` spread, a package-specific `transform`, and `writeTo`
|
|
513
|
+
* delegating to `defaultWriteMemoriesToOrchestrator` (issue #2794).
|
|
514
|
+
*/
|
|
515
|
+
export function defineFileImporterAdapter<Parsed>(
|
|
516
|
+
definition: FileImporterDefinition<Parsed>,
|
|
517
|
+
): ImporterAdapter<Parsed> {
|
|
518
|
+
return {
|
|
519
|
+
name: definition.name,
|
|
520
|
+
sourceLabel: definition.sourceLabel,
|
|
521
|
+
parse(input, options) {
|
|
522
|
+
return definition.parse(input, {
|
|
523
|
+
...(options?.strict !== undefined ? { strict: options.strict } : {}),
|
|
524
|
+
...(options?.filePath !== undefined ? { filePath: options.filePath } : {}),
|
|
525
|
+
});
|
|
526
|
+
},
|
|
527
|
+
transform: (parsed, options) => definition.transform(parsed, options),
|
|
528
|
+
writeTo: defaultWriteMemoriesToOrchestrator,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Runtime barrel purity (issue #2794, fix round)
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// `makeImporterTestTarget` / `loadImporterFixture` live behind the
|
|
5
|
+
// `@remnic/core/importers/test-utils` subpath ONLY. A static re-export from
|
|
6
|
+
// this barrel would link test-utils.js — and its node:fs/node:path/node:url
|
|
7
|
+
// imports — into every production consumer of `@remnic/core/importers`.
|
|
8
|
+
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { test } from "node:test";
|
|
12
|
+
|
|
13
|
+
import * as barrel from "./index.js";
|
|
14
|
+
|
|
15
|
+
// Namespace record read is deliberate: the harness names must be ABSENT, so
|
|
16
|
+
// they cannot appear on the typed surface of the barrel.
|
|
17
|
+
const surface = barrel as Record<string, unknown>;
|
|
18
|
+
|
|
19
|
+
const barrelSource = readFileSync(
|
|
20
|
+
new URL("./index.ts", import.meta.url),
|
|
21
|
+
"utf-8",
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
test("runtime barrel does not export the test harness (#2794)", () => {
|
|
25
|
+
assert.equal(
|
|
26
|
+
surface.makeImporterTestTarget,
|
|
27
|
+
undefined,
|
|
28
|
+
"test harness must stay behind @remnic/core/importers/test-utils",
|
|
29
|
+
);
|
|
30
|
+
assert.equal(
|
|
31
|
+
surface.loadImporterFixture,
|
|
32
|
+
undefined,
|
|
33
|
+
"test harness must stay behind @remnic/core/importers/test-utils",
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("runtime barrel statically links no test-utils module (#2794)", () => {
|
|
38
|
+
assert.ok(
|
|
39
|
+
!barrelSource.includes("test-utils"),
|
|
40
|
+
"src/importers/index.ts must not reference ./test-utils.js — import @remnic/core/importers/test-utils instead",
|
|
41
|
+
);
|
|
42
|
+
});
|