@remnic/core 1.1.2 → 1.1.4
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/abort-error.js +1 -0
- package/dist/abstraction-nodes.js +1 -0
- package/dist/access-audit.js +1 -0
- package/dist/access-cli.js +72 -47
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +50 -5
- package/dist/access-http.js +39 -16
- package/dist/access-idempotency.js +1 -0
- package/dist/access-mcp.d.ts +10 -5
- package/dist/access-mcp.js +38 -14
- package/dist/access-schema.d.ts +133 -13
- package/dist/access-schema.js +20 -1
- package/dist/access-service-CtXFnprR.d.ts +2033 -0
- package/dist/access-service.d.ts +11 -6
- package/dist/access-service.js +40 -15
- package/dist/active-memory-bridge.js +1 -0
- package/dist/active-recall.js +1 -0
- package/dist/active-recall.js.map +1 -1
- package/dist/behavior-learner.js +1 -0
- package/dist/behavior-learner.js.map +1 -1
- package/dist/behavior-signals.js +1 -0
- package/dist/bootstrap.d.ts +6 -4
- package/dist/bootstrap.js +1 -0
- package/dist/boxes.js +1 -0
- package/dist/briefing.d.ts +9 -5
- package/dist/briefing.js +10 -7
- package/dist/buffer-surprise-report.js +1 -0
- package/dist/buffer-surprise.js +1 -0
- package/dist/buffer.d.ts +1 -1
- package/dist/buffer.js +1 -0
- package/dist/calibration.d.ts +8 -1
- package/dist/calibration.js +10 -2
- package/dist/calibration.js.map +1 -1
- package/dist/capsule-cli.d.ts +137 -0
- package/dist/capsule-cli.js +34 -0
- package/dist/capsule-crypto-5CYAGVC5.js +18 -0
- package/dist/capsule-export-NZQPOTQ4.js +17 -0
- package/dist/capsule-export-NZQPOTQ4.js.map +1 -0
- package/dist/capsule-import-SDCUXLEV.js +16 -0
- package/dist/capsule-import-SDCUXLEV.js.map +1 -0
- package/dist/capsule-merge-DI7PNQ2H.js +189 -0
- package/dist/capsule-merge-DI7PNQ2H.js.map +1 -0
- package/dist/causal-behavior.js +1 -0
- package/dist/causal-behavior.js.map +1 -1
- package/dist/causal-chain.js +1 -0
- package/dist/causal-consolidation.js +12 -9
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-retrieval.js +2 -1
- package/dist/causal-retrieval.js.map +1 -1
- package/dist/causal-trajectory-graph.js +4 -1
- package/dist/causal-trajectory-graph.js.map +1 -1
- package/dist/causal-trajectory.js +2 -1
- package/dist/chunk-2LSZVONP.js +67 -0
- package/dist/chunk-2LSZVONP.js.map +1 -0
- package/dist/chunk-32KD5IHZ.js +245 -0
- package/dist/chunk-32KD5IHZ.js.map +1 -0
- package/dist/chunk-3KIS4VGT.js +228 -0
- package/dist/chunk-3KIS4VGT.js.map +1 -0
- package/dist/chunk-3LCWFNVS.js +350 -0
- package/dist/chunk-3LCWFNVS.js.map +1 -0
- package/dist/chunk-43EKP2UK.js +26 -0
- package/dist/chunk-43EKP2UK.js.map +1 -0
- package/dist/chunk-457A4P3L.js +119 -0
- package/dist/chunk-457A4P3L.js.map +1 -0
- package/dist/{chunk-TMYO7B5P.js → chunk-47WOM4YW.js} +2 -2
- package/dist/{chunk-FVA6TGI3.js → chunk-52PDY6GD.js} +42 -2
- package/dist/chunk-52PDY6GD.js.map +1 -0
- package/dist/{chunk-ULYOGL6R.js → chunk-5HRY2WRF.js} +7 -3
- package/dist/chunk-5HRY2WRF.js.map +1 -0
- package/dist/{chunk-BOUYNNYD.js → chunk-67YLUWLG.js} +32 -13
- package/dist/{chunk-BOUYNNYD.js.map → chunk-67YLUWLG.js.map} +1 -1
- package/dist/chunk-6TBWYBJ3.js +236 -0
- package/dist/chunk-6TBWYBJ3.js.map +1 -0
- package/dist/chunk-74EMIVE4.js +329 -0
- package/dist/chunk-74EMIVE4.js.map +1 -0
- package/dist/chunk-74WWN7ZW.js +82 -0
- package/dist/chunk-74WWN7ZW.js.map +1 -0
- package/dist/chunk-A6XUJE5D.js +126 -0
- package/dist/chunk-A6XUJE5D.js.map +1 -0
- package/dist/{chunk-STGWEHYR.js → chunk-AEMBDV7M.js} +1187 -62
- package/dist/chunk-AEMBDV7M.js.map +1 -0
- package/dist/{chunk-PVICZTKG.js → chunk-AGZHRWPT.js} +5 -5
- package/dist/{chunk-PVICZTKG.js.map → chunk-AGZHRWPT.js.map} +1 -1
- package/dist/chunk-AJA46VX5.js +393 -0
- package/dist/chunk-AJA46VX5.js.map +1 -0
- package/dist/chunk-ASIQZXYO.js +277 -0
- package/dist/chunk-ASIQZXYO.js.map +1 -0
- package/dist/{chunk-DG6YMRDC.js → chunk-B2TL6GA2.js} +2 -2
- package/dist/chunk-BJMBJZ2Y.js +290 -0
- package/dist/chunk-BJMBJZ2Y.js.map +1 -0
- package/dist/chunk-BT7NVCML.js +79 -0
- package/dist/chunk-BT7NVCML.js.map +1 -0
- package/dist/chunk-CK5NTM2S.js +454 -0
- package/dist/chunk-CK5NTM2S.js.map +1 -0
- package/dist/{chunk-AYXIPSZO.js → chunk-CRU27Q4J.js} +2 -2
- package/dist/{chunk-UWB5LMWY.js → chunk-CUI2STX6.js} +526 -24
- package/dist/chunk-CUI2STX6.js.map +1 -0
- package/dist/{chunk-CUPFXL3J.js → chunk-EGEPUGN4.js} +4 -4
- package/dist/chunk-EGEPUGN4.js.map +1 -0
- package/dist/{chunk-3OGMS3PE.js → chunk-F5VQOQ2E.js} +3 -2
- package/dist/chunk-F5VQOQ2E.js.map +1 -0
- package/dist/chunk-FP2373TW.js +149 -0
- package/dist/chunk-FP2373TW.js.map +1 -0
- package/dist/{chunk-RBBWYEFJ.js → chunk-G2WADRQ3.js} +1 -1
- package/dist/chunk-G7D6GZ5J.js +48 -0
- package/dist/chunk-G7D6GZ5J.js.map +1 -0
- package/dist/chunk-H7XKCNR6.js +60 -0
- package/dist/chunk-H7XKCNR6.js.map +1 -0
- package/dist/{chunk-LOIMBRDE.js → chunk-HIRKCQGF.js} +1994 -412
- package/dist/chunk-HIRKCQGF.js.map +1 -0
- package/dist/chunk-IXEJRKCZ.js +18 -0
- package/dist/chunk-IXEJRKCZ.js.map +1 -0
- package/dist/chunk-IYY4MCPG.js +275 -0
- package/dist/chunk-IYY4MCPG.js.map +1 -0
- package/dist/{chunk-BECYBZLX.js → chunk-JWSENLQI.js} +502 -22
- package/dist/chunk-JWSENLQI.js.map +1 -0
- package/dist/chunk-KNKUID7G.js +183 -0
- package/dist/chunk-KNKUID7G.js.map +1 -0
- package/dist/chunk-L2IO2QPY.js +2036 -0
- package/dist/chunk-L2IO2QPY.js.map +1 -0
- package/dist/{chunk-ZAIM4TUE.js → chunk-LW2NMHDW.js} +46 -1
- package/dist/chunk-LW2NMHDW.js.map +1 -0
- package/dist/chunk-MDYG7VI7.js +48 -0
- package/dist/chunk-MDYG7VI7.js.map +1 -0
- package/dist/{chunk-VDX363PS.js → chunk-MUELDH4F.js} +10 -3
- package/dist/chunk-MUELDH4F.js.map +1 -0
- package/dist/chunk-MXC3AP5I.js +74 -0
- package/dist/chunk-MXC3AP5I.js.map +1 -0
- package/dist/chunk-NN3TS5BM.js +147 -0
- package/dist/chunk-NN3TS5BM.js.map +1 -0
- package/dist/{chunk-3YGHKTBF.js → chunk-NZS2BLTP.js} +963 -326
- package/dist/chunk-NZS2BLTP.js.map +1 -0
- package/dist/chunk-OA3L7BFR.js +183 -0
- package/dist/chunk-OA3L7BFR.js.map +1 -0
- package/dist/chunk-OZHRDTDX.js +240 -0
- package/dist/chunk-OZHRDTDX.js.map +1 -0
- package/dist/chunk-PCUKNJAZ.js +165 -0
- package/dist/chunk-PCUKNJAZ.js.map +1 -0
- package/dist/{chunk-6PFRXT4K.js → chunk-PFV5C235.js} +11 -6
- package/dist/chunk-PFV5C235.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/{chunk-Y7R2XJ5Q.js → chunk-Q7FJ5ZHM.js} +6 -2
- package/dist/chunk-Q7FJ5ZHM.js.map +1 -0
- package/dist/{chunk-WCLICCGB.js → chunk-RILIVK4O.js} +91 -4
- package/dist/chunk-RILIVK4O.js.map +1 -0
- package/dist/{chunk-C2EFFULQ.js → chunk-RK2Y4XOM.js} +163 -20
- package/dist/chunk-RK2Y4XOM.js.map +1 -0
- package/dist/{chunk-TP4FZJIZ.js → chunk-RULE4VG5.js} +5 -1
- package/dist/chunk-RULE4VG5.js.map +1 -0
- package/dist/{chunk-PVPWZSSI.js → chunk-SMA4IMHV.js} +19 -3
- package/dist/chunk-SMA4IMHV.js.map +1 -0
- package/dist/{chunk-6YJHX2DL.js → chunk-TIFRGAKO.js} +242 -22
- package/dist/chunk-TIFRGAKO.js.map +1 -0
- package/dist/chunk-TUFG6VXY.js +875 -0
- package/dist/chunk-TUFG6VXY.js.map +1 -0
- package/dist/chunk-TYEOAFH3.js +251 -0
- package/dist/chunk-TYEOAFH3.js.map +1 -0
- package/dist/chunk-UKJAGEXH.js +260 -0
- package/dist/chunk-UKJAGEXH.js.map +1 -0
- package/dist/{chunk-KVBLZUKV.js → chunk-USFPPRAF.js} +93 -3
- package/dist/chunk-USFPPRAF.js.map +1 -0
- package/dist/{chunk-NBVAS5MT.js → chunk-V7TEH5I2.js} +6 -6
- package/dist/{chunk-GA5P7RST.js → chunk-VTJVUHRK.js} +22 -36
- package/dist/chunk-VTJVUHRK.js.map +1 -0
- package/dist/{chunk-SPI27QT6.js → chunk-W7WWT4FJ.js} +9 -4
- package/dist/chunk-W7WWT4FJ.js.map +1 -0
- package/dist/chunk-WIICJPET.js +45 -0
- package/dist/chunk-WIICJPET.js.map +1 -0
- package/dist/{chunk-VBVG2M5G.js → chunk-WPGJYVUH.js} +6 -2
- package/dist/chunk-WPGJYVUH.js.map +1 -0
- package/dist/{chunk-4HQS2HPX.js → chunk-WSZIHQBK.js} +29 -9
- package/dist/{chunk-4HQS2HPX.js.map → chunk-WSZIHQBK.js.map} +1 -1
- package/dist/{chunk-NZLQTHS5.js → chunk-WW3QQF4H.js} +4 -1
- package/dist/chunk-WW3QQF4H.js.map +1 -0
- package/dist/{chunk-DIXB44VE.js → chunk-X6VBWOVZ.js} +28 -13
- package/dist/chunk-X6VBWOVZ.js.map +1 -0
- package/dist/{chunk-XXVWLXSG.js → chunk-XQ4EJLUD.js} +64 -92
- package/dist/chunk-XQ4EJLUD.js.map +1 -0
- package/dist/{chunk-OC5OXUQ4.js → chunk-XRCYKJ3V.js} +780 -17
- package/dist/chunk-XRCYKJ3V.js.map +1 -0
- package/dist/{chunk-F5VP6YCB.js → chunk-Y4A6M3B6.js} +573 -156
- package/dist/chunk-Y4A6M3B6.js.map +1 -0
- package/dist/chunk-YNJHCGDT.js +309 -0
- package/dist/chunk-YNJHCGDT.js.map +1 -0
- package/dist/{chunk-L7IXWRYE.js → chunk-ZIBOQULP.js} +22 -13
- package/dist/chunk-ZIBOQULP.js.map +1 -0
- package/dist/{chunk-W6SL7OFG.js → chunk-ZTSE2ZJ6.js} +12 -2
- package/dist/{chunk-W6SL7OFG.js.map → chunk-ZTSE2ZJ6.js.map} +1 -1
- package/dist/chunking.js +1 -0
- package/dist/cipher-GVE2GQ5H.js +28 -0
- package/dist/cipher-GVE2GQ5H.js.map +1 -0
- package/dist/citations.js +1 -0
- package/dist/{cli-BkeRaYfk.d.ts → cli-lMql2FCr.d.ts} +26 -7
- package/dist/cli.d.ts +11 -6
- package/dist/cli.js +69 -34
- package/dist/codex-thread-key.js +1 -0
- package/dist/commitment-ledger.js +1 -0
- package/dist/compression-optimizer.js +1 -0
- package/dist/config.d.ts +2 -1
- package/dist/config.js +4 -1
- package/dist/connectors-cli-DFGtY2DB.d.ts +257 -0
- package/dist/connectors-cli.d.ts +2 -0
- package/dist/connectors-cli.js +22 -0
- package/dist/connectors-cli.js.map +1 -0
- package/dist/consolidation-operator.d.ts +65 -5
- package/dist/consolidation-operator.js +6 -1
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-provenance-check.js +3 -2
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/consolidation-undo.js +1 -0
- package/dist/consolidation-undo.js.map +1 -1
- package/dist/{contradiction-review-WIUBAR52.js → contradiction-review-5LTTVDQV.js} +2 -1
- package/dist/contradiction-review-5LTTVDQV.js.map +1 -0
- package/dist/{contradiction-scan-E3GJTI4F.js → contradiction-scan-3Z6YW7YA.js} +2 -1
- package/dist/{contradiction-scan-E3GJTI4F.js.map → contradiction-scan-3Z6YW7YA.js.map} +1 -1
- package/dist/cross-namespace-budget.js +1 -0
- package/dist/cue-anchors.js +1 -0
- package/dist/dashboard-runtime.js +1 -0
- package/dist/day-summary.js +1 -0
- package/dist/delinearize.js +1 -0
- package/dist/direct-answer-wiring.js +1 -0
- package/dist/direct-answer.js +1 -0
- package/dist/dreams-ledger-LR2NBAZE.js +286 -0
- package/dist/dreams-ledger-LR2NBAZE.js.map +1 -0
- package/dist/embedding-fallback.js +1 -0
- package/dist/engine-O6YWKQM3.js +28 -0
- package/dist/engine-O6YWKQM3.js.map +1 -0
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +10 -7
- package/dist/entity-schema.js +1 -0
- package/dist/evals.js +1 -0
- package/dist/evidence-pack.d.ts +16 -0
- package/dist/evidence-pack.js +8 -0
- package/dist/evidence-pack.js.map +1 -0
- package/dist/explicit-capture.d.ts +6 -4
- package/dist/explicit-capture.js +1 -0
- package/dist/extraction-judge-telemetry.js +1 -0
- package/dist/extraction-judge-training.js +1 -0
- package/dist/extraction-judge.js +1 -0
- package/dist/extraction.js +8 -7
- package/dist/fallback-llm.js +3 -2
- package/dist/first-start-migration-4MHQEOSD.js +263 -0
- package/dist/first-start-migration-4MHQEOSD.js.map +1 -0
- package/dist/forget-PLR6J5DN.js +69 -0
- package/dist/forget-PLR6J5DN.js.map +1 -0
- package/dist/framework-CyHYDcri.d.ts +153 -0
- package/dist/fs-utils-IRVUFB6G.js +30 -0
- package/dist/fs-utils-IRVUFB6G.js.map +1 -0
- package/dist/graph-dashboard-diff.js +1 -0
- package/dist/graph-dashboard-key.js +1 -0
- package/dist/graph-dashboard-parser.js +1 -0
- package/dist/graph-edge-decay-PWB63GRE.js +207 -0
- package/dist/graph-edge-decay-PWB63GRE.js.map +1 -0
- package/dist/graph-edge-reinforcement.d.ts +81 -0
- package/dist/graph-edge-reinforcement.js +24 -0
- package/dist/graph-edge-reinforcement.js.map +1 -0
- package/dist/graph-events.d.ts +87 -0
- package/dist/graph-events.js +14 -0
- package/dist/graph-events.js.map +1 -0
- package/dist/graph-recall.js +1 -0
- package/dist/graph-retrieval.js +1 -0
- package/dist/graph-snapshot.d.ts +112 -0
- package/dist/graph-snapshot.js +19 -0
- package/dist/graph-snapshot.js.map +1 -0
- package/dist/graph.d.ts +105 -7
- package/dist/graph.js +20 -3
- package/dist/harmonic-retrieval.js +1 -0
- package/dist/himem.js +1 -0
- package/dist/hygiene.js +1 -0
- package/dist/identity-continuity.js +1 -0
- package/dist/importance.js +1 -0
- package/dist/index.d.ts +574 -13
- package/dist/index.js +337 -69
- package/dist/index.js.map +1 -1
- package/dist/intent.js +1 -0
- package/dist/json-extract.js +1 -0
- package/dist/json-store.js +1 -0
- package/dist/kdf-7S6RWKLZ.js +26 -0
- package/dist/kdf-7S6RWKLZ.js.map +1 -0
- package/dist/legacy-hook-compat.js +1 -0
- package/dist/legacy-hook-compat.js.map +1 -1
- package/dist/lifecycle.js +1 -0
- package/dist/live-connectors-runner.d.ts +48 -0
- package/dist/live-connectors-runner.js +17 -0
- package/dist/live-connectors-runner.js.map +1 -0
- package/dist/local-llm.js +1 -0
- package/dist/logger.js +1 -0
- package/dist/memory-action-policy.js +1 -0
- package/dist/memory-cache.d.ts +2 -1
- package/dist/memory-cache.js +4 -1
- package/dist/memory-governance-JZHZDOLN.js +37 -0
- package/dist/memory-governance-JZHZDOLN.js.map +1 -0
- package/dist/memory-lifecycle-ledger-utils.d.ts +2 -1
- package/dist/memory-lifecycle-ledger-utils.js +4 -1
- package/dist/memory-projection-format.js +1 -0
- package/dist/{memory-projection-store-DeSXPh1j.d.ts → memory-projection-store-CY8TU40w.d.ts} +2 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-projection-store.js +2 -1
- package/dist/memory-worth-bench.js +1 -0
- package/dist/memory-worth-bench.js.map +1 -1
- package/dist/memory-worth-filter.js +1 -0
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/memory-worth-outcomes.js +1 -0
- package/dist/memory-worth.js +1 -0
- package/dist/metadata-FC3XPDRQ.js +21 -0
- package/dist/metadata-FC3XPDRQ.js.map +1 -0
- package/dist/migrate-from-identity-anchor-TTEDEJGX.js +8 -0
- package/dist/migrate-from-identity-anchor-TTEDEJGX.js.map +1 -0
- package/dist/model-registry.js +1 -0
- package/dist/models-json.js +1 -0
- package/dist/native-knowledge.js +1 -0
- package/dist/negative.js +1 -0
- package/dist/objective-state-writers.js +1 -0
- package/dist/objective-state-writers.js.map +1 -1
- package/dist/objective-state.js +1 -0
- package/dist/openai-chat-compat.js +1 -0
- package/dist/operator-toolkit.d.ts +46 -2
- package/dist/operator-toolkit.js +29 -17
- package/dist/opik-exporter.js +1 -0
- package/dist/opik-exporter.js.map +1 -1
- package/dist/{orchestrator-CmJ-NTdJ.d.ts → orchestrator-ChkesB8U.d.ts} +177 -13
- package/dist/orchestrator.d.ts +6 -4
- package/dist/orchestrator.js +57 -41
- package/dist/page-versioning.js +1 -0
- package/dist/path-RMTY5Y5A.js +9 -0
- package/dist/path-RMTY5Y5A.js.map +1 -0
- package/dist/patterns-cli.d.ts +160 -0
- package/dist/patterns-cli.js +29 -0
- package/dist/patterns-cli.js.map +1 -0
- package/dist/peers-6OSQ3NK6.js +44 -0
- package/dist/peers-6OSQ3NK6.js.map +1 -0
- package/dist/plugin-id.js +1 -0
- package/dist/policy-runtime.js +1 -0
- package/dist/{port-BADbLZU5.d.ts → port-hqGnoStS.d.ts} +6 -0
- package/dist/profiling.js +1 -0
- package/dist/purge-6ATBGT77.js +205 -0
- package/dist/purge-6ATBGT77.js.map +1 -0
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd-recall-cache.js +1 -0
- package/dist/qmd.d.ts +2 -1
- package/dist/qmd.js +4 -3
- package/dist/reasoning-trace-recall.js +1 -0
- package/dist/reasoning-trace-types.js +1 -0
- package/dist/recall-audit-anomaly.js +1 -0
- package/dist/recall-audit.js +1 -0
- package/dist/recall-disclosure-escalation.d.ts +84 -0
- package/dist/recall-disclosure-escalation.js +14 -0
- package/dist/recall-disclosure-escalation.js.map +1 -0
- package/dist/recall-explain-renderer.js +4 -1
- package/dist/recall-mmr.js +1 -0
- package/dist/recall-qos.js +1 -0
- package/dist/recall-query-policy.js +1 -0
- package/dist/recall-state.d.ts +7 -0
- package/dist/recall-state.js +2 -1
- package/dist/recall-tag-filter.d.ts +56 -0
- package/dist/recall-tag-filter.js +14 -0
- package/dist/recall-tag-filter.js.map +1 -0
- package/dist/recall-tokenization.js +1 -0
- package/dist/recall-xray-cli.d.ts +9 -2
- package/dist/recall-xray-cli.js +9 -4
- package/dist/recall-xray-renderer.js +4 -1
- package/dist/recall-xray.d.ts +116 -2
- package/dist/recall-xray.js +9 -3
- package/dist/reconstruct.js +1 -0
- package/dist/release-changelog.js +2 -0
- package/dist/release-changelog.js.map +1 -1
- package/dist/relevance.js +1 -0
- package/dist/rerank.js +1 -0
- package/dist/{resolution-QBTDHTG7.js → resolution-YGIBORXI.js} +2 -1
- package/dist/{resolution-QBTDHTG7.js.map → resolution-YGIBORXI.js.map} +1 -1
- package/dist/resolve-auth-token.d.ts +51 -0
- package/dist/resolve-auth-token.js +12 -0
- package/dist/resolve-auth-token.js.map +1 -0
- package/dist/resolve-provider-secret.d.ts +9 -1
- package/dist/resolve-provider-secret.js +4 -1
- package/dist/resume-bundles.js +4 -3
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-agents.js +1 -0
- package/dist/retrieval-tiers.js +1 -0
- package/dist/retrieval.js +1 -0
- package/dist/sanitize.js +1 -0
- package/dist/schemas.d.ts +15 -2
- package/dist/schemas.js +2 -1
- package/dist/sdk-compat.js +1 -0
- package/dist/sdk-compat.js.map +1 -1
- package/dist/secure-store-4R2GSO7S.js +156 -0
- package/dist/secure-store-4R2GSO7S.js.map +1 -0
- package/dist/semantic-chunking.js +1 -0
- package/dist/{semantic-consolidation-CxJU6MJk.d.ts → semantic-consolidation-ByBXb-sf.d.ts} +3 -3
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +12 -7
- package/dist/semantic-rule-promotion.d.ts +1 -1
- package/dist/semantic-rule-promotion.js +10 -7
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +10 -7
- package/dist/session-integrity.js +1 -0
- package/dist/session-observer-bands.js +1 -0
- package/dist/session-observer-state.js +1 -0
- package/dist/session-toggles.js +2 -0
- package/dist/session-toggles.js.map +1 -1
- package/dist/signal.js +1 -0
- package/dist/skills-registry.js +2 -0
- package/dist/skills-registry.js.map +1 -1
- package/dist/source-attribution.js +1 -0
- package/dist/state-NCHQ4TRG.js +8 -0
- package/dist/state-NCHQ4TRG.js.map +1 -0
- package/dist/state-store-3EH7HYIN.js +16 -0
- package/dist/state-store-3EH7HYIN.js.map +1 -0
- package/dist/storage.d.ts +76 -2
- package/dist/storage.js +9 -6
- package/dist/store-contract.js +1 -0
- package/dist/summarizer.js +5 -4
- package/dist/summary-snapshot.js +1 -0
- package/dist/temporal-index.js +1 -0
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-supersession.js +2 -1
- package/dist/temporal-validity.d.ts +52 -0
- package/dist/temporal-validity.js +14 -0
- package/dist/temporal-validity.js.map +1 -0
- package/dist/threading.js +1 -0
- package/dist/tier-migration.d.ts +2 -2
- package/dist/tier-migration.js +1 -0
- package/dist/tier-routing.js +1 -0
- package/dist/tier-stats-62ZVDFKS.js +152 -0
- package/dist/tier-stats-62ZVDFKS.js.map +1 -0
- package/dist/tmt.js +1 -0
- package/dist/tokens.js +1 -0
- package/dist/topics.js +1 -0
- package/dist/trace-C5ETWBEF.js +290 -0
- package/dist/trace-C5ETWBEF.js.map +1 -0
- package/dist/transcript.js +1 -0
- package/dist/trust-zones.js +1 -0
- package/dist/tui-RI7P6PBS.js +13 -0
- package/dist/tui-RI7P6PBS.js.map +1 -0
- package/dist/types-V3FJ26TF.js +30 -0
- package/dist/types-V3FJ26TF.js.map +1 -0
- package/dist/types.d.ts +634 -9
- package/dist/types.js +10 -3
- package/dist/utility-learner.js +1 -0
- package/dist/utility-runtime.js +1 -0
- package/dist/utility-telemetry.js +1 -0
- package/dist/verified-recall.js +10 -7
- package/dist/version-utils.js +1 -0
- package/dist/whitespace.js +1 -0
- package/dist/work-product-ledger.js +1 -0
- package/package.json +7 -3
- package/scripts/ensure-better-sqlite3.mjs +124 -0
- package/dist/access-service-Br8ZydTK.d.ts +0 -827
- package/dist/chunk-3OGMS3PE.js.map +0 -1
- package/dist/chunk-3YGHKTBF.js.map +0 -1
- package/dist/chunk-6PFRXT4K.js.map +0 -1
- package/dist/chunk-6YJHX2DL.js.map +0 -1
- package/dist/chunk-BECYBZLX.js.map +0 -1
- package/dist/chunk-C2EFFULQ.js.map +0 -1
- package/dist/chunk-CUPFXL3J.js.map +0 -1
- package/dist/chunk-DIXB44VE.js.map +0 -1
- package/dist/chunk-F5VP6YCB.js.map +0 -1
- package/dist/chunk-FVA6TGI3.js.map +0 -1
- package/dist/chunk-GA5P7RST.js.map +0 -1
- package/dist/chunk-KVBLZUKV.js.map +0 -1
- package/dist/chunk-L7IXWRYE.js.map +0 -1
- package/dist/chunk-LOIMBRDE.js.map +0 -1
- package/dist/chunk-LTCGGW2D.js +0 -14
- package/dist/chunk-LTCGGW2D.js.map +0 -1
- package/dist/chunk-NZLQTHS5.js.map +0 -1
- package/dist/chunk-OC5OXUQ4.js.map +0 -1
- package/dist/chunk-PVPWZSSI.js.map +0 -1
- package/dist/chunk-SPI27QT6.js.map +0 -1
- package/dist/chunk-STGWEHYR.js.map +0 -1
- package/dist/chunk-TP4FZJIZ.js.map +0 -1
- package/dist/chunk-ULYOGL6R.js.map +0 -1
- package/dist/chunk-UWB5LMWY.js.map +0 -1
- package/dist/chunk-VBVG2M5G.js.map +0 -1
- package/dist/chunk-VDX363PS.js.map +0 -1
- package/dist/chunk-WCLICCGB.js.map +0 -1
- package/dist/chunk-X6GF3FX2.js +0 -26
- package/dist/chunk-X6GF3FX2.js.map +0 -1
- package/dist/chunk-XXVWLXSG.js.map +0 -1
- package/dist/chunk-Y7R2XJ5Q.js.map +0 -1
- package/dist/chunk-ZAIM4TUE.js.map +0 -1
- package/dist/engine-72LSIWQP.js +0 -23
- /package/dist/{contradiction-review-WIUBAR52.js.map → capsule-cli.js.map} +0 -0
- /package/dist/{engine-72LSIWQP.js.map → capsule-crypto-5CYAGVC5.js.map} +0 -0
- /package/dist/{chunk-TMYO7B5P.js.map → chunk-47WOM4YW.js.map} +0 -0
- /package/dist/{chunk-DG6YMRDC.js.map → chunk-B2TL6GA2.js.map} +0 -0
- /package/dist/{chunk-AYXIPSZO.js.map → chunk-CRU27Q4J.js.map} +0 -0
- /package/dist/{chunk-RBBWYEFJ.js.map → chunk-G2WADRQ3.js.map} +0 -0
- /package/dist/{chunk-NBVAS5MT.js.map → chunk-V7TEH5I2.js.map} +0 -0
|
@@ -0,0 +1,2036 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isValidConnectorId
|
|
3
|
+
} from "./chunk-6TBWYBJ3.js";
|
|
4
|
+
|
|
5
|
+
// src/connectors/live/registry.ts
|
|
6
|
+
var LiveConnectorRegistryError = class extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "LiveConnectorRegistryError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var LiveConnectorRegistry = class {
|
|
13
|
+
connectors = /* @__PURE__ */ new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Register a connector. Throws `LiveConnectorRegistryError` if the id is
|
|
16
|
+
* malformed or already registered.
|
|
17
|
+
*
|
|
18
|
+
* Re-registration is rejected (rather than silently overwriting) because
|
|
19
|
+
* silent overwrites mask plugin loading bugs in development and could let
|
|
20
|
+
* a malicious extension shadow a built-in connector.
|
|
21
|
+
*/
|
|
22
|
+
register(connector) {
|
|
23
|
+
if (!connector || typeof connector !== "object") {
|
|
24
|
+
throw new LiveConnectorRegistryError(
|
|
25
|
+
"register(): connector must be a non-null object"
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
if (!isValidConnectorId(connector.id)) {
|
|
29
|
+
throw new LiveConnectorRegistryError(
|
|
30
|
+
`register(): invalid connector id ${JSON.stringify(connector.id)} \u2014 must match /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
if (this.connectors.has(connector.id)) {
|
|
34
|
+
throw new LiveConnectorRegistryError(
|
|
35
|
+
`register(): connector id ${JSON.stringify(connector.id)} is already registered`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (typeof connector.displayName !== "string" || connector.displayName.length === 0) {
|
|
39
|
+
throw new LiveConnectorRegistryError(
|
|
40
|
+
`register(): connector ${connector.id} missing displayName`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
if (typeof connector.validateConfig !== "function") {
|
|
44
|
+
throw new LiveConnectorRegistryError(
|
|
45
|
+
`register(): connector ${connector.id} missing validateConfig()`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
if (typeof connector.syncIncremental !== "function") {
|
|
49
|
+
throw new LiveConnectorRegistryError(
|
|
50
|
+
`register(): connector ${connector.id} missing syncIncremental()`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
this.connectors.set(connector.id, connector);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Look up a connector by id. Returns `undefined` if not registered.
|
|
57
|
+
*/
|
|
58
|
+
get(id) {
|
|
59
|
+
return this.connectors.get(id);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Return all registered connectors, sorted by id for stable enumeration.
|
|
63
|
+
*/
|
|
64
|
+
list() {
|
|
65
|
+
return Array.from(this.connectors.values()).sort((a, b) => a.id.localeCompare(b.id));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Remove a connector. Returns `true` if a connector was removed, `false`
|
|
69
|
+
* otherwise. The cursor / state file on disk is **not** touched — callers
|
|
70
|
+
* who want to fully decommission a connector must also delete its state
|
|
71
|
+
* file via the `state-store` module.
|
|
72
|
+
*/
|
|
73
|
+
unregister(id) {
|
|
74
|
+
return this.connectors.delete(id);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Number of registered connectors. Cheap; safe to call frequently.
|
|
78
|
+
*/
|
|
79
|
+
size() {
|
|
80
|
+
return this.connectors.size;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// src/connectors/live/transient-errors.ts
|
|
85
|
+
var TRANSIENT_NODE_CODES = /* @__PURE__ */ new Set([
|
|
86
|
+
"ECONNRESET",
|
|
87
|
+
"ECONNREFUSED",
|
|
88
|
+
"ECONNABORTED",
|
|
89
|
+
"ETIMEDOUT",
|
|
90
|
+
"ESOCKETTIMEDOUT",
|
|
91
|
+
"ENOTFOUND",
|
|
92
|
+
"EAI_AGAIN",
|
|
93
|
+
"EPIPE",
|
|
94
|
+
"EHOSTUNREACH",
|
|
95
|
+
"ENETUNREACH",
|
|
96
|
+
"ENETDOWN",
|
|
97
|
+
"ERR_NETWORK",
|
|
98
|
+
"ERR_NETWORK_CHANGED"
|
|
99
|
+
]);
|
|
100
|
+
function resolveHttpStatus(e, statusProps) {
|
|
101
|
+
for (const prop of statusProps) {
|
|
102
|
+
const v = e[prop];
|
|
103
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
104
|
+
}
|
|
105
|
+
const responseStatus = e.response?.status;
|
|
106
|
+
if (typeof responseStatus === "number" && Number.isFinite(responseStatus)) {
|
|
107
|
+
return responseStatus;
|
|
108
|
+
}
|
|
109
|
+
if (typeof e.status === "number" && Number.isFinite(e.status)) return e.status;
|
|
110
|
+
if (typeof e.code === "number" && Number.isFinite(e.code)) return e.code;
|
|
111
|
+
if (typeof e.code === "string" && /^\d+$/.test(e.code)) {
|
|
112
|
+
const n = Number(e.code);
|
|
113
|
+
if (Number.isFinite(n) && n >= 100 && n <= 599) return n;
|
|
114
|
+
}
|
|
115
|
+
return void 0;
|
|
116
|
+
}
|
|
117
|
+
function isTransientHttpError(err, statusProps = []) {
|
|
118
|
+
if (err === null || typeof err !== "object") return false;
|
|
119
|
+
const e = err;
|
|
120
|
+
if (typeof e.name === "string" && e.name === "AbortError") return true;
|
|
121
|
+
const status = resolveHttpStatus(e, statusProps);
|
|
122
|
+
if (status !== void 0) {
|
|
123
|
+
if (status === 429) return true;
|
|
124
|
+
if (status >= 500 && status <= 599) return true;
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
if (typeof e.code === "string") {
|
|
128
|
+
if (TRANSIENT_NODE_CODES.has(e.code)) return true;
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/connectors/live/google-drive.ts
|
|
135
|
+
var GOOGLE_DRIVE_CONNECTOR_ID = "google-drive";
|
|
136
|
+
var GOOGLE_DRIVE_CURSOR_KIND = "drivePageToken";
|
|
137
|
+
var DEFAULT_POLL_INTERVAL_MS = 5 * 60 * 1e3;
|
|
138
|
+
var MAX_POLL_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
139
|
+
var MAX_TEXT_BYTES = 5 * 1024 * 1024;
|
|
140
|
+
var MAX_CHANGES_PER_PASS = 200;
|
|
141
|
+
var FOLDER_ID_PATTERN = /^[A-Za-z0-9_-]{8,256}$/;
|
|
142
|
+
var GOOGLE_NATIVE_EXPORT_MIME = Object.freeze({
|
|
143
|
+
"application/vnd.google-apps.document": "text/plain",
|
|
144
|
+
"application/vnd.google-apps.spreadsheet": "text/csv",
|
|
145
|
+
"application/vnd.google-apps.presentation": "text/plain"
|
|
146
|
+
});
|
|
147
|
+
var TEXT_MIME_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
148
|
+
"text/plain",
|
|
149
|
+
"text/markdown",
|
|
150
|
+
"text/csv",
|
|
151
|
+
"text/html",
|
|
152
|
+
"application/json",
|
|
153
|
+
"application/xml",
|
|
154
|
+
"text/xml"
|
|
155
|
+
]);
|
|
156
|
+
function validateGoogleDriveConfig(raw) {
|
|
157
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
158
|
+
throw new TypeError(
|
|
159
|
+
`googleDrive: config must be an object, got ${raw === null ? "null" : Array.isArray(raw) ? "array" : typeof raw}`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
const r = raw;
|
|
163
|
+
const clientId = requireNonEmptyString(r.clientId, "clientId");
|
|
164
|
+
const clientSecret = requireNonEmptyString(r.clientSecret, "clientSecret");
|
|
165
|
+
const refreshToken = requireNonEmptyString(r.refreshToken, "refreshToken");
|
|
166
|
+
let pollIntervalMs;
|
|
167
|
+
if (r.pollIntervalMs === void 0) {
|
|
168
|
+
pollIntervalMs = DEFAULT_POLL_INTERVAL_MS;
|
|
169
|
+
} else if (typeof r.pollIntervalMs !== "number" || !Number.isFinite(r.pollIntervalMs)) {
|
|
170
|
+
throw new TypeError(
|
|
171
|
+
`googleDrive: pollIntervalMs must be a finite number (got ${JSON.stringify(r.pollIntervalMs)})`
|
|
172
|
+
);
|
|
173
|
+
} else if (!Number.isInteger(r.pollIntervalMs)) {
|
|
174
|
+
throw new TypeError(
|
|
175
|
+
`googleDrive: pollIntervalMs must be an integer (got ${r.pollIntervalMs})`
|
|
176
|
+
);
|
|
177
|
+
} else if (r.pollIntervalMs < 1e3) {
|
|
178
|
+
throw new RangeError(
|
|
179
|
+
`googleDrive: pollIntervalMs must be \u22651000ms; got ${r.pollIntervalMs}`
|
|
180
|
+
);
|
|
181
|
+
} else if (r.pollIntervalMs > MAX_POLL_INTERVAL_MS) {
|
|
182
|
+
throw new RangeError(
|
|
183
|
+
`googleDrive: pollIntervalMs must be \u2264${MAX_POLL_INTERVAL_MS} (24h); got ${r.pollIntervalMs}`
|
|
184
|
+
);
|
|
185
|
+
} else {
|
|
186
|
+
pollIntervalMs = r.pollIntervalMs;
|
|
187
|
+
}
|
|
188
|
+
let folderIds = [];
|
|
189
|
+
if (r.folderIds !== void 0) {
|
|
190
|
+
if (!Array.isArray(r.folderIds)) {
|
|
191
|
+
throw new TypeError(
|
|
192
|
+
`googleDrive: folderIds must be an array of strings (got ${typeof r.folderIds})`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const seen = /* @__PURE__ */ new Set();
|
|
196
|
+
const out = [];
|
|
197
|
+
for (const value of r.folderIds) {
|
|
198
|
+
if (typeof value !== "string") {
|
|
199
|
+
throw new TypeError(
|
|
200
|
+
`googleDrive: folderIds entries must be strings; found ${typeof value}`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
const trimmed = value.trim();
|
|
204
|
+
if (!FOLDER_ID_PATTERN.test(trimmed)) {
|
|
205
|
+
throw new RangeError(
|
|
206
|
+
`googleDrive: folderIds entry ${JSON.stringify(value)} is not a valid Drive folder id`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
if (seen.has(trimmed)) continue;
|
|
210
|
+
seen.add(trimmed);
|
|
211
|
+
out.push(trimmed);
|
|
212
|
+
}
|
|
213
|
+
folderIds = Object.freeze(out);
|
|
214
|
+
}
|
|
215
|
+
return Object.freeze({
|
|
216
|
+
clientId,
|
|
217
|
+
clientSecret,
|
|
218
|
+
refreshToken,
|
|
219
|
+
pollIntervalMs,
|
|
220
|
+
folderIds
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
function requireNonEmptyString(value, key) {
|
|
224
|
+
if (typeof value !== "string") {
|
|
225
|
+
throw new TypeError(
|
|
226
|
+
`googleDrive: ${key} must be a string (got ${typeof value})`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
const trimmed = value.trim();
|
|
230
|
+
if (trimmed.length === 0) {
|
|
231
|
+
throw new RangeError(`googleDrive: ${key} must be non-empty`);
|
|
232
|
+
}
|
|
233
|
+
return trimmed;
|
|
234
|
+
}
|
|
235
|
+
function decideChange(change, folderScope) {
|
|
236
|
+
if (change.removed === true) return { kind: "skip-removed" };
|
|
237
|
+
const file = change.file;
|
|
238
|
+
if (!file || typeof file.id !== "string") return { kind: "skip-removed" };
|
|
239
|
+
if (file.trashed === true) return { kind: "skip-trashed" };
|
|
240
|
+
if (folderScope.size > 0) {
|
|
241
|
+
const parents = file.parents ?? [];
|
|
242
|
+
let intersects = false;
|
|
243
|
+
for (const parent of parents) {
|
|
244
|
+
if (folderScope.has(parent)) {
|
|
245
|
+
intersects = true;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (!intersects) return { kind: "skip-folder-scope" };
|
|
250
|
+
}
|
|
251
|
+
if (typeof file.size === "number" && file.size > MAX_TEXT_BYTES) {
|
|
252
|
+
return { kind: "skip-too-large" };
|
|
253
|
+
}
|
|
254
|
+
if (typeof file.size === "string") {
|
|
255
|
+
const sizeNum = Number(file.size);
|
|
256
|
+
if (Number.isFinite(sizeNum) && sizeNum > MAX_TEXT_BYTES) {
|
|
257
|
+
return { kind: "skip-too-large" };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const mime = file.mimeType;
|
|
261
|
+
if (typeof mime !== "string" || mime.length === 0) {
|
|
262
|
+
return { kind: "skip-binary" };
|
|
263
|
+
}
|
|
264
|
+
const exportMime = GOOGLE_NATIVE_EXPORT_MIME[mime];
|
|
265
|
+
if (typeof exportMime === "string") {
|
|
266
|
+
return { kind: "import", file, mode: "export", exportMime };
|
|
267
|
+
}
|
|
268
|
+
if (TEXT_MIME_ALLOWLIST.has(mime) || mime.startsWith("text/")) {
|
|
269
|
+
return { kind: "import", file, mode: "media" };
|
|
270
|
+
}
|
|
271
|
+
return { kind: "skip-binary" };
|
|
272
|
+
}
|
|
273
|
+
function createGoogleDriveConnector(options = {}) {
|
|
274
|
+
const clientFactory = options.clientFactory ?? defaultGoogleDriveClientFactory;
|
|
275
|
+
return {
|
|
276
|
+
id: GOOGLE_DRIVE_CONNECTOR_ID,
|
|
277
|
+
displayName: "Google Drive",
|
|
278
|
+
description: "Imports text content (Google Docs/Sheets/Slides + plain text) from a user's Drive into Remnic.",
|
|
279
|
+
validateConfig(raw) {
|
|
280
|
+
return validateGoogleDriveConfig(raw);
|
|
281
|
+
},
|
|
282
|
+
async syncIncremental(args) {
|
|
283
|
+
const config = validateGoogleDriveConfig(args.config);
|
|
284
|
+
throwIfAborted(args.abortSignal);
|
|
285
|
+
const client = await clientFactory(config);
|
|
286
|
+
throwIfAborted(args.abortSignal);
|
|
287
|
+
let pageToken;
|
|
288
|
+
const isFirstSync = args.cursor === null;
|
|
289
|
+
if (isFirstSync) {
|
|
290
|
+
const seed = await client.getStartPageToken();
|
|
291
|
+
if (typeof seed?.startPageToken !== "string" || seed.startPageToken.length === 0) {
|
|
292
|
+
throw new Error("googleDrive: drive.changes.getStartPageToken returned an empty token");
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
newDocs: [],
|
|
296
|
+
nextCursor: makeCursor(seed.startPageToken)
|
|
297
|
+
};
|
|
298
|
+
} else if (args.cursor.kind !== GOOGLE_DRIVE_CURSOR_KIND) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`googleDrive: unexpected cursor kind ${JSON.stringify(args.cursor.kind)}; expected ${GOOGLE_DRIVE_CURSOR_KIND}`
|
|
301
|
+
);
|
|
302
|
+
} else {
|
|
303
|
+
pageToken = args.cursor.value;
|
|
304
|
+
}
|
|
305
|
+
const folderScope = new Set(config.folderIds);
|
|
306
|
+
const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
307
|
+
const newDocs = [];
|
|
308
|
+
let skippedBinary = 0;
|
|
309
|
+
let skippedFolderScope = 0;
|
|
310
|
+
let skippedTooLarge = 0;
|
|
311
|
+
let consumed = 0;
|
|
312
|
+
let resolvedNextToken;
|
|
313
|
+
while (true) {
|
|
314
|
+
throwIfAborted(args.abortSignal);
|
|
315
|
+
const remaining = MAX_CHANGES_PER_PASS - consumed;
|
|
316
|
+
if (remaining <= 0) {
|
|
317
|
+
resolvedNextToken = pageToken;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
const pageSize = Math.min(100, remaining);
|
|
321
|
+
const page = await client.listChanges({ pageToken, pageSize });
|
|
322
|
+
for (const change of page.changes) {
|
|
323
|
+
throwIfAborted(args.abortSignal);
|
|
324
|
+
consumed++;
|
|
325
|
+
const decision = decideChange(change, folderScope);
|
|
326
|
+
switch (decision.kind) {
|
|
327
|
+
case "import": {
|
|
328
|
+
const doc = await fetchDocument(client, decision, fetchedAt, args.abortSignal);
|
|
329
|
+
if (doc) newDocs.push(doc);
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
case "skip-binary":
|
|
333
|
+
skippedBinary++;
|
|
334
|
+
break;
|
|
335
|
+
case "skip-folder-scope":
|
|
336
|
+
skippedFolderScope++;
|
|
337
|
+
break;
|
|
338
|
+
case "skip-too-large":
|
|
339
|
+
skippedTooLarge++;
|
|
340
|
+
break;
|
|
341
|
+
// skip-removed / skip-trashed are intentionally not counted —
|
|
342
|
+
// they're upstream-driven and noisy.
|
|
343
|
+
default:
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (typeof page.newStartPageToken === "string" && page.newStartPageToken.length > 0) {
|
|
348
|
+
resolvedNextToken = page.newStartPageToken;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
if (typeof page.nextPageToken === "string" && page.nextPageToken.length > 0) {
|
|
352
|
+
pageToken = page.nextPageToken;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
resolvedNextToken = pageToken;
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
const nextCursor = makeCursor(
|
|
359
|
+
resolvedNextToken ?? pageToken
|
|
360
|
+
);
|
|
361
|
+
const result = {
|
|
362
|
+
newDocs,
|
|
363
|
+
nextCursor,
|
|
364
|
+
skippedBinary,
|
|
365
|
+
skippedFolderScope,
|
|
366
|
+
skippedTooLarge
|
|
367
|
+
};
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function makeCursor(value) {
|
|
373
|
+
return {
|
|
374
|
+
kind: GOOGLE_DRIVE_CURSOR_KIND,
|
|
375
|
+
value,
|
|
376
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
async function fetchDocument(client, decision, fetchedAt, abortSignal) {
|
|
380
|
+
throwIfAborted(abortSignal);
|
|
381
|
+
const file = decision.file;
|
|
382
|
+
let body;
|
|
383
|
+
try {
|
|
384
|
+
body = decision.mode === "export" ? await client.exportFile({
|
|
385
|
+
fileId: file.id,
|
|
386
|
+
mimeType: decision.exportMime ?? "text/plain"
|
|
387
|
+
}) : await client.getFileMedia({ fileId: file.id });
|
|
388
|
+
} catch (err) {
|
|
389
|
+
if (isTransientDriveError(err)) {
|
|
390
|
+
throw err;
|
|
391
|
+
}
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
if (typeof body !== "string" || body.length === 0) return null;
|
|
395
|
+
if (body.length > MAX_TEXT_BYTES) return null;
|
|
396
|
+
return {
|
|
397
|
+
id: file.id,
|
|
398
|
+
title: typeof file.name === "string" && file.name.length > 0 ? file.name : void 0,
|
|
399
|
+
content: body,
|
|
400
|
+
source: {
|
|
401
|
+
connector: GOOGLE_DRIVE_CONNECTOR_ID,
|
|
402
|
+
externalId: file.id,
|
|
403
|
+
externalRevision: typeof file.modifiedTime === "string" ? file.modifiedTime : void 0,
|
|
404
|
+
externalUrl: typeof file.webViewLink === "string" ? file.webViewLink : void 0,
|
|
405
|
+
fetchedAt
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function throwIfAborted(signal) {
|
|
410
|
+
if (signal?.aborted) {
|
|
411
|
+
const err = new Error("googleDrive: sync aborted");
|
|
412
|
+
err.name = "AbortError";
|
|
413
|
+
throw err;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function isTransientDriveError(err) {
|
|
417
|
+
return isTransientHttpError(err);
|
|
418
|
+
}
|
|
419
|
+
var defaultGoogleDriveClientFactory = async (config) => {
|
|
420
|
+
const specifier = "googleapis";
|
|
421
|
+
let mod;
|
|
422
|
+
try {
|
|
423
|
+
mod = await import(
|
|
424
|
+
/* @vite-ignore */
|
|
425
|
+
specifier
|
|
426
|
+
);
|
|
427
|
+
} catch (err) {
|
|
428
|
+
throw new Error(
|
|
429
|
+
`googleDrive: optional peer dependency \`googleapis\` is not installed. Run \`npm install googleapis\` (or \`pnpm add googleapis\`) in the host package to enable the Google Drive connector. (underlying: ${err.message})`
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
const { google } = mod;
|
|
433
|
+
const oauth = new google.auth.OAuth2({
|
|
434
|
+
clientId: config.clientId,
|
|
435
|
+
clientSecret: config.clientSecret
|
|
436
|
+
});
|
|
437
|
+
oauth.setCredentials({ refresh_token: config.refreshToken });
|
|
438
|
+
const drive = google.drive({ version: "v3", auth: oauth });
|
|
439
|
+
return {
|
|
440
|
+
async getStartPageToken() {
|
|
441
|
+
const res = await drive.changes.getStartPageToken({});
|
|
442
|
+
return { startPageToken: String(res.data.startPageToken ?? "") };
|
|
443
|
+
},
|
|
444
|
+
async listChanges({ pageToken, pageSize }) {
|
|
445
|
+
const res = await drive.changes.list({
|
|
446
|
+
pageToken,
|
|
447
|
+
pageSize,
|
|
448
|
+
fields: "newStartPageToken, nextPageToken, changes(removed, fileId, file(id, name, mimeType, modifiedTime, trashed, parents, webViewLink, size))",
|
|
449
|
+
spaces: "drive",
|
|
450
|
+
includeRemoved: true
|
|
451
|
+
});
|
|
452
|
+
const data = res.data ?? {};
|
|
453
|
+
return {
|
|
454
|
+
changes: data.changes ?? [],
|
|
455
|
+
newStartPageToken: data.newStartPageToken ?? void 0,
|
|
456
|
+
nextPageToken: data.nextPageToken ?? void 0
|
|
457
|
+
};
|
|
458
|
+
},
|
|
459
|
+
async exportFile({ fileId, mimeType }) {
|
|
460
|
+
const res = await drive.files.export(
|
|
461
|
+
{ fileId, mimeType },
|
|
462
|
+
{ responseType: "text" }
|
|
463
|
+
);
|
|
464
|
+
return typeof res.data === "string" ? res.data : String(res.data ?? "");
|
|
465
|
+
},
|
|
466
|
+
async getFileMedia({ fileId }) {
|
|
467
|
+
const res = await drive.files.get(
|
|
468
|
+
{ fileId, alt: "media" },
|
|
469
|
+
{ responseType: "text" }
|
|
470
|
+
);
|
|
471
|
+
return typeof res.data === "string" ? res.data : String(res.data ?? "");
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// src/connectors/live/notion.ts
|
|
477
|
+
var NOTION_CONNECTOR_ID = "notion";
|
|
478
|
+
var NOTION_CURSOR_KIND = "notionWatermark";
|
|
479
|
+
var NOTION_DEFAULT_POLL_INTERVAL_MS = 5 * 60 * 1e3;
|
|
480
|
+
var NOTION_MAX_POLL_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
481
|
+
var MAX_TEXT_BYTES2 = 5 * 1024 * 1024;
|
|
482
|
+
var MAX_BLOCK_DEPTH = 5;
|
|
483
|
+
var MAX_PAGES_PER_PASS = 200;
|
|
484
|
+
var NOTION_TOKEN_PREFIX = "secret_";
|
|
485
|
+
function validateNotionConfig(raw) {
|
|
486
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
487
|
+
throw new TypeError(
|
|
488
|
+
`notion: config must be an object, got ${raw === null ? "null" : Array.isArray(raw) ? "array" : typeof raw}`
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
const r = raw;
|
|
492
|
+
if (typeof r.token !== "string") {
|
|
493
|
+
throw new TypeError(`notion: token must be a string (got ${typeof r.token})`);
|
|
494
|
+
}
|
|
495
|
+
const token = r.token.trim();
|
|
496
|
+
if (token.length === 0) {
|
|
497
|
+
throw new RangeError("notion: token must be non-empty");
|
|
498
|
+
}
|
|
499
|
+
if (!token.startsWith(NOTION_TOKEN_PREFIX)) {
|
|
500
|
+
throw new RangeError(
|
|
501
|
+
`notion: token must start with "${NOTION_TOKEN_PREFIX}" (looks like a non-integration token)`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
let pollIntervalMs;
|
|
505
|
+
if (r.pollIntervalMs === void 0) {
|
|
506
|
+
pollIntervalMs = NOTION_DEFAULT_POLL_INTERVAL_MS;
|
|
507
|
+
} else if (typeof r.pollIntervalMs !== "number" || !Number.isFinite(r.pollIntervalMs)) {
|
|
508
|
+
throw new TypeError(
|
|
509
|
+
`notion: pollIntervalMs must be a finite number (got ${JSON.stringify(r.pollIntervalMs)})`
|
|
510
|
+
);
|
|
511
|
+
} else if (!Number.isInteger(r.pollIntervalMs)) {
|
|
512
|
+
throw new TypeError(`notion: pollIntervalMs must be an integer (got ${r.pollIntervalMs})`);
|
|
513
|
+
} else if (r.pollIntervalMs < 1e3) {
|
|
514
|
+
throw new RangeError(`notion: pollIntervalMs must be \u22651000ms; got ${r.pollIntervalMs}`);
|
|
515
|
+
} else if (r.pollIntervalMs > NOTION_MAX_POLL_INTERVAL_MS) {
|
|
516
|
+
throw new RangeError(
|
|
517
|
+
`notion: pollIntervalMs must be \u2264${NOTION_MAX_POLL_INTERVAL_MS} (24h); got ${r.pollIntervalMs}`
|
|
518
|
+
);
|
|
519
|
+
} else {
|
|
520
|
+
pollIntervalMs = r.pollIntervalMs;
|
|
521
|
+
}
|
|
522
|
+
let databaseIds = [];
|
|
523
|
+
if (r.databaseIds !== void 0) {
|
|
524
|
+
if (!Array.isArray(r.databaseIds)) {
|
|
525
|
+
throw new TypeError(
|
|
526
|
+
`notion: databaseIds must be an array of strings (got ${typeof r.databaseIds})`
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
const seen = /* @__PURE__ */ new Set();
|
|
530
|
+
const out = [];
|
|
531
|
+
for (const value of r.databaseIds) {
|
|
532
|
+
if (typeof value !== "string") {
|
|
533
|
+
throw new TypeError(
|
|
534
|
+
`notion: databaseIds entries must be strings; found ${typeof value}`
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
const trimmed = value.trim();
|
|
538
|
+
if (!isValidNotionId(trimmed)) {
|
|
539
|
+
throw new RangeError(
|
|
540
|
+
`notion: databaseIds entry ${JSON.stringify(value)} is not a valid Notion id`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
if (seen.has(trimmed)) continue;
|
|
544
|
+
seen.add(trimmed);
|
|
545
|
+
out.push(trimmed);
|
|
546
|
+
}
|
|
547
|
+
databaseIds = Object.freeze(out);
|
|
548
|
+
}
|
|
549
|
+
return Object.freeze({ token, databaseIds, pollIntervalMs });
|
|
550
|
+
}
|
|
551
|
+
function isValidNotionId(value) {
|
|
552
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)) {
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
if (/^[0-9a-f]{32}$/i.test(value)) {
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
function makeCursor2(payload) {
|
|
561
|
+
return {
|
|
562
|
+
kind: NOTION_CURSOR_KIND,
|
|
563
|
+
value: JSON.stringify(payload),
|
|
564
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
function parseCursorPayload(cursor) {
|
|
568
|
+
if (cursor.kind !== NOTION_CURSOR_KIND) {
|
|
569
|
+
throw new Error(
|
|
570
|
+
`notion: unexpected cursor kind ${JSON.stringify(cursor.kind)}; expected ${NOTION_CURSOR_KIND}`
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
let parsed;
|
|
574
|
+
try {
|
|
575
|
+
parsed = JSON.parse(cursor.value);
|
|
576
|
+
} catch {
|
|
577
|
+
throw new Error(`notion: cursor value is not valid JSON`);
|
|
578
|
+
}
|
|
579
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
580
|
+
throw new Error(`notion: cursor value does not match NotionCursorPayload shape`);
|
|
581
|
+
}
|
|
582
|
+
const p = parsed;
|
|
583
|
+
const pages = typeof p.pages === "object" && p.pages !== null && !Array.isArray(p.pages) ? p.pages : {};
|
|
584
|
+
const databases = typeof p.databases === "object" && p.databases !== null && !Array.isArray(p.databases) ? p.databases : {};
|
|
585
|
+
return { pages, databases };
|
|
586
|
+
}
|
|
587
|
+
function isTransientNotionError(err) {
|
|
588
|
+
return isTransientHttpError(err, ["notionStatus"]);
|
|
589
|
+
}
|
|
590
|
+
var NOTION_BASE_URL = "https://api.notion.com/v1";
|
|
591
|
+
var NOTION_API_VERSION = "2022-06-28";
|
|
592
|
+
function throwIfAborted2(signal) {
|
|
593
|
+
if (signal?.aborted) {
|
|
594
|
+
const err = new Error("notion: sync aborted");
|
|
595
|
+
err.name = "AbortError";
|
|
596
|
+
throw err;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
function makeNotionApiError(apiErr) {
|
|
600
|
+
const err = new Error(
|
|
601
|
+
`notion: API error ${apiErr.status} (${apiErr.code}): ${apiErr.message}`
|
|
602
|
+
);
|
|
603
|
+
err.notionStatus = apiErr.status;
|
|
604
|
+
return err;
|
|
605
|
+
}
|
|
606
|
+
async function notionFetch(fetchFn, token, path, body, signal) {
|
|
607
|
+
const url = `${NOTION_BASE_URL}${path}`;
|
|
608
|
+
const res = await fetchFn(url, {
|
|
609
|
+
method: "POST",
|
|
610
|
+
headers: {
|
|
611
|
+
Authorization: `Bearer ${token}`,
|
|
612
|
+
"Notion-Version": NOTION_API_VERSION,
|
|
613
|
+
"Content-Type": "application/json"
|
|
614
|
+
},
|
|
615
|
+
body: JSON.stringify(body),
|
|
616
|
+
signal
|
|
617
|
+
});
|
|
618
|
+
const data = await res.json();
|
|
619
|
+
if (!res.ok) {
|
|
620
|
+
if (typeof data === "object" && data !== null && data.object === "error") {
|
|
621
|
+
throw makeNotionApiError(data);
|
|
622
|
+
}
|
|
623
|
+
const err = new Error(`notion: HTTP ${res.status}`);
|
|
624
|
+
err.notionStatus = res.status;
|
|
625
|
+
throw err;
|
|
626
|
+
}
|
|
627
|
+
return data;
|
|
628
|
+
}
|
|
629
|
+
async function notionGet(fetchFn, token, path, signal) {
|
|
630
|
+
const url = `${NOTION_BASE_URL}${path}`;
|
|
631
|
+
const res = await fetchFn(url, {
|
|
632
|
+
method: "GET",
|
|
633
|
+
headers: {
|
|
634
|
+
Authorization: `Bearer ${token}`,
|
|
635
|
+
"Notion-Version": NOTION_API_VERSION
|
|
636
|
+
},
|
|
637
|
+
signal
|
|
638
|
+
});
|
|
639
|
+
const data = await res.json();
|
|
640
|
+
if (!res.ok) {
|
|
641
|
+
if (typeof data === "object" && data !== null && data.object === "error") {
|
|
642
|
+
throw makeNotionApiError(data);
|
|
643
|
+
}
|
|
644
|
+
const err = new Error(`notion: HTTP ${res.status}`);
|
|
645
|
+
err.notionStatus = res.status;
|
|
646
|
+
throw err;
|
|
647
|
+
}
|
|
648
|
+
return data;
|
|
649
|
+
}
|
|
650
|
+
function extractRichText(richText) {
|
|
651
|
+
if (!Array.isArray(richText)) return "";
|
|
652
|
+
return richText.map((span) => {
|
|
653
|
+
if (typeof span !== "object" || span === null) return "";
|
|
654
|
+
return typeof span.plain_text === "string" ? span.plain_text : "";
|
|
655
|
+
}).join("");
|
|
656
|
+
}
|
|
657
|
+
function extractBlockText(block) {
|
|
658
|
+
const type = block.type;
|
|
659
|
+
const blockData = block[type];
|
|
660
|
+
if (typeof blockData !== "object" || blockData === null) return "";
|
|
661
|
+
const data = blockData;
|
|
662
|
+
if (Array.isArray(data.rich_text)) {
|
|
663
|
+
const text = extractRichText(data.rich_text);
|
|
664
|
+
if (text.length > 0) {
|
|
665
|
+
if (type === "heading_1") return `# ${text}`;
|
|
666
|
+
if (type === "heading_2") return `## ${text}`;
|
|
667
|
+
if (type === "heading_3") return `### ${text}`;
|
|
668
|
+
if (type === "to_do") {
|
|
669
|
+
const checked = data.checked === true;
|
|
670
|
+
return `- [${checked ? "x" : " "}] ${text}`;
|
|
671
|
+
}
|
|
672
|
+
if (type === "bulleted_list_item" || type === "numbered_list_item") {
|
|
673
|
+
return `- ${text}`;
|
|
674
|
+
}
|
|
675
|
+
return text;
|
|
676
|
+
}
|
|
677
|
+
return "";
|
|
678
|
+
}
|
|
679
|
+
if (type === "code" && Array.isArray(data.rich_text)) {
|
|
680
|
+
return extractRichText(data.rich_text);
|
|
681
|
+
}
|
|
682
|
+
return "";
|
|
683
|
+
}
|
|
684
|
+
async function fetchPageText(fetchFn, token, blockId, depth, signal) {
|
|
685
|
+
if (depth > MAX_BLOCK_DEPTH) return "";
|
|
686
|
+
throwIfAborted2(signal);
|
|
687
|
+
const lines = [];
|
|
688
|
+
let cursor = void 0;
|
|
689
|
+
while (true) {
|
|
690
|
+
throwIfAborted2(signal);
|
|
691
|
+
const pathQuery = cursor ? `/blocks/${blockId}/children?page_size=100&start_cursor=${encodeURIComponent(cursor)}` : `/blocks/${blockId}/children?page_size=100`;
|
|
692
|
+
const data = await notionGet(fetchFn, token, pathQuery, signal);
|
|
693
|
+
if (typeof data !== "object" || data === null) break;
|
|
694
|
+
const page = data;
|
|
695
|
+
for (const block of page.results ?? []) {
|
|
696
|
+
throwIfAborted2(signal);
|
|
697
|
+
const text = extractBlockText(block);
|
|
698
|
+
if (text.length > 0) lines.push(text);
|
|
699
|
+
if (block.has_children && depth < MAX_BLOCK_DEPTH) {
|
|
700
|
+
const childText = await fetchPageText(
|
|
701
|
+
fetchFn,
|
|
702
|
+
token,
|
|
703
|
+
block.id,
|
|
704
|
+
depth + 1,
|
|
705
|
+
signal
|
|
706
|
+
);
|
|
707
|
+
if (childText.length > 0) lines.push(childText);
|
|
708
|
+
}
|
|
709
|
+
const currentSize = lines.reduce((acc, l) => acc + l.length + 1, 0);
|
|
710
|
+
if (currentSize >= MAX_TEXT_BYTES2) break;
|
|
711
|
+
}
|
|
712
|
+
if (!page.has_more || typeof page.next_cursor !== "string" || page.next_cursor === null) {
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
cursor = page.next_cursor;
|
|
716
|
+
}
|
|
717
|
+
return lines.join("\n");
|
|
718
|
+
}
|
|
719
|
+
function extractPageTitle(page) {
|
|
720
|
+
if (typeof page.properties !== "object" || page.properties === null) return void 0;
|
|
721
|
+
for (const prop of Object.values(page.properties)) {
|
|
722
|
+
if (prop.type === "title" && Array.isArray(prop.title)) {
|
|
723
|
+
const text = prop.title.map((t) => t.plain_text ?? "").join("");
|
|
724
|
+
if (text.trim().length > 0) return text.trim();
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return void 0;
|
|
728
|
+
}
|
|
729
|
+
function createNotionConnector(options = {}) {
|
|
730
|
+
const fetchFn = options.fetchFn ?? // Use global fetch (available in Node.js 18+). The cast is safe — we
|
|
731
|
+
// only use the subset defined in `NotionFetchFn`.
|
|
732
|
+
globalThis.fetch;
|
|
733
|
+
return {
|
|
734
|
+
id: NOTION_CONNECTOR_ID,
|
|
735
|
+
displayName: "Notion",
|
|
736
|
+
description: "Imports text content from Notion database pages into Remnic on a poll schedule.",
|
|
737
|
+
validateConfig(raw) {
|
|
738
|
+
return validateNotionConfig(raw);
|
|
739
|
+
},
|
|
740
|
+
async syncIncremental(args) {
|
|
741
|
+
const config = validateNotionConfig(args.config);
|
|
742
|
+
throwIfAborted2(args.abortSignal);
|
|
743
|
+
if (config.databaseIds.length === 0) {
|
|
744
|
+
const emptyPayload = { pages: {}, databases: {} };
|
|
745
|
+
const result = {
|
|
746
|
+
newDocs: [],
|
|
747
|
+
nextCursor: makeCursor2(emptyPayload),
|
|
748
|
+
skippedUnchanged: 0,
|
|
749
|
+
skippedTooLarge: 0,
|
|
750
|
+
skippedEmpty: 0
|
|
751
|
+
};
|
|
752
|
+
return result;
|
|
753
|
+
}
|
|
754
|
+
const isFirstSync = args.cursor === null;
|
|
755
|
+
const payload = isFirstSync ? { pages: {}, databases: {} } : parseCursorPayload(args.cursor);
|
|
756
|
+
if (isFirstSync) {
|
|
757
|
+
const seedPayload = await seedWatermark(
|
|
758
|
+
fetchFn,
|
|
759
|
+
config,
|
|
760
|
+
payload,
|
|
761
|
+
args.abortSignal
|
|
762
|
+
);
|
|
763
|
+
return {
|
|
764
|
+
newDocs: [],
|
|
765
|
+
nextCursor: makeCursor2(seedPayload),
|
|
766
|
+
skippedUnchanged: 0,
|
|
767
|
+
skippedTooLarge: 0,
|
|
768
|
+
skippedEmpty: 0
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
return await incrementalSync(fetchFn, config, payload, args.abortSignal);
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
async function seedWatermark(fetchFn, config, payload, signal) {
|
|
776
|
+
const pages = { ...payload.pages };
|
|
777
|
+
const databases = { ...payload.databases };
|
|
778
|
+
for (const dbId of config.databaseIds) {
|
|
779
|
+
throwIfAborted2(signal);
|
|
780
|
+
let notionCursor = void 0;
|
|
781
|
+
let latestInDb = "";
|
|
782
|
+
while (true) {
|
|
783
|
+
throwIfAborted2(signal);
|
|
784
|
+
const body = { page_size: 100, sorts: [] };
|
|
785
|
+
if (notionCursor) body.start_cursor = notionCursor;
|
|
786
|
+
const data = await notionFetch(
|
|
787
|
+
fetchFn,
|
|
788
|
+
config.token,
|
|
789
|
+
`/databases/${dbId}/query`,
|
|
790
|
+
body,
|
|
791
|
+
signal
|
|
792
|
+
);
|
|
793
|
+
if (typeof data !== "object" || data === null) break;
|
|
794
|
+
const page = data;
|
|
795
|
+
for (const p of page.results ?? []) {
|
|
796
|
+
if (typeof p.id === "string" && typeof p.last_edited_time === "string") {
|
|
797
|
+
pages[p.id] = p.last_edited_time;
|
|
798
|
+
if (!latestInDb || p.last_edited_time > latestInDb) {
|
|
799
|
+
latestInDb = p.last_edited_time;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
if (!page.has_more || typeof page.next_cursor !== "string" || page.next_cursor === null) {
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
notionCursor = page.next_cursor;
|
|
807
|
+
}
|
|
808
|
+
if (latestInDb) databases[dbId] = latestInDb;
|
|
809
|
+
}
|
|
810
|
+
return { pages, databases };
|
|
811
|
+
}
|
|
812
|
+
async function incrementalSync(fetchFn, config, payload, signal) {
|
|
813
|
+
const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
814
|
+
const newDocs = [];
|
|
815
|
+
const updatedPages = { ...payload.pages };
|
|
816
|
+
const updatedDatabases = { ...payload.databases };
|
|
817
|
+
let skippedUnchanged = 0;
|
|
818
|
+
let skippedTooLarge = 0;
|
|
819
|
+
let skippedEmpty = 0;
|
|
820
|
+
let totalConsumed = 0;
|
|
821
|
+
for (const dbId of config.databaseIds) {
|
|
822
|
+
throwIfAborted2(signal);
|
|
823
|
+
if (totalConsumed >= MAX_PAGES_PER_PASS) break;
|
|
824
|
+
const dbWatermark = payload.databases[dbId];
|
|
825
|
+
let notionCursor = void 0;
|
|
826
|
+
let latestInDb = dbWatermark ?? "";
|
|
827
|
+
let databaseFullyDrained = false;
|
|
828
|
+
while (true) {
|
|
829
|
+
throwIfAborted2(signal);
|
|
830
|
+
if (totalConsumed >= MAX_PAGES_PER_PASS) break;
|
|
831
|
+
const body = {
|
|
832
|
+
page_size: 100,
|
|
833
|
+
sorts: [
|
|
834
|
+
{
|
|
835
|
+
timestamp: "last_edited_time",
|
|
836
|
+
direction: "descending"
|
|
837
|
+
}
|
|
838
|
+
]
|
|
839
|
+
};
|
|
840
|
+
if (dbWatermark) {
|
|
841
|
+
body.filter = {
|
|
842
|
+
timestamp: "last_edited_time",
|
|
843
|
+
last_edited_time: { after: dbWatermark }
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
if (notionCursor) body.start_cursor = notionCursor;
|
|
847
|
+
const data = await notionFetch(
|
|
848
|
+
fetchFn,
|
|
849
|
+
config.token,
|
|
850
|
+
`/databases/${dbId}/query`,
|
|
851
|
+
body,
|
|
852
|
+
signal
|
|
853
|
+
);
|
|
854
|
+
if (typeof data !== "object" || data === null) break;
|
|
855
|
+
const pageResp = data;
|
|
856
|
+
let cutoffMidPage = false;
|
|
857
|
+
for (const notionPage of pageResp.results ?? []) {
|
|
858
|
+
throwIfAborted2(signal);
|
|
859
|
+
totalConsumed++;
|
|
860
|
+
const pageId = notionPage.id;
|
|
861
|
+
const lastEdited = notionPage.last_edited_time;
|
|
862
|
+
if (typeof pageId !== "string" || typeof lastEdited !== "string") continue;
|
|
863
|
+
const knownRevision = payload.pages[pageId];
|
|
864
|
+
if (knownRevision && knownRevision >= lastEdited) {
|
|
865
|
+
skippedUnchanged++;
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
const doc = await fetchPageDocument(
|
|
869
|
+
fetchFn,
|
|
870
|
+
config.token,
|
|
871
|
+
notionPage,
|
|
872
|
+
fetchedAt,
|
|
873
|
+
signal
|
|
874
|
+
);
|
|
875
|
+
if (doc === "too-large") {
|
|
876
|
+
skippedTooLarge++;
|
|
877
|
+
updatedPages[pageId] = lastEdited;
|
|
878
|
+
} else if (doc === "empty") {
|
|
879
|
+
skippedEmpty++;
|
|
880
|
+
updatedPages[pageId] = lastEdited;
|
|
881
|
+
} else if (doc !== null) {
|
|
882
|
+
newDocs.push(doc);
|
|
883
|
+
updatedPages[pageId] = lastEdited;
|
|
884
|
+
if (!latestInDb || lastEdited > latestInDb) {
|
|
885
|
+
latestInDb = lastEdited;
|
|
886
|
+
}
|
|
887
|
+
} else {
|
|
888
|
+
updatedPages[pageId] = lastEdited;
|
|
889
|
+
}
|
|
890
|
+
if (totalConsumed >= MAX_PAGES_PER_PASS) {
|
|
891
|
+
cutoffMidPage = true;
|
|
892
|
+
break;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
if (cutoffMidPage) {
|
|
896
|
+
break;
|
|
897
|
+
}
|
|
898
|
+
if (pageResp.has_more === true) {
|
|
899
|
+
if (typeof pageResp.next_cursor === "string" && pageResp.next_cursor.length > 0) {
|
|
900
|
+
notionCursor = pageResp.next_cursor;
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
break;
|
|
904
|
+
}
|
|
905
|
+
databaseFullyDrained = true;
|
|
906
|
+
break;
|
|
907
|
+
}
|
|
908
|
+
if (databaseFullyDrained && latestInDb) {
|
|
909
|
+
updatedDatabases[dbId] = latestInDb;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
const nextCursor = makeCursor2({ pages: updatedPages, databases: updatedDatabases });
|
|
913
|
+
return {
|
|
914
|
+
newDocs,
|
|
915
|
+
nextCursor,
|
|
916
|
+
skippedUnchanged,
|
|
917
|
+
skippedTooLarge,
|
|
918
|
+
skippedEmpty
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
async function fetchPageDocument(fetchFn, token, notionPage, fetchedAt, signal) {
|
|
922
|
+
throwIfAborted2(signal);
|
|
923
|
+
let text;
|
|
924
|
+
try {
|
|
925
|
+
text = await fetchPageText(fetchFn, token, notionPage.id, 0, signal);
|
|
926
|
+
} catch (err) {
|
|
927
|
+
if (isTransientNotionError(err)) {
|
|
928
|
+
throw err;
|
|
929
|
+
}
|
|
930
|
+
return null;
|
|
931
|
+
}
|
|
932
|
+
if (typeof text !== "string" || text.trim().length === 0) return "empty";
|
|
933
|
+
if (text.length > MAX_TEXT_BYTES2) return "too-large";
|
|
934
|
+
const title = extractPageTitle(notionPage);
|
|
935
|
+
return {
|
|
936
|
+
id: notionPage.id,
|
|
937
|
+
title,
|
|
938
|
+
content: text,
|
|
939
|
+
source: {
|
|
940
|
+
connector: NOTION_CONNECTOR_ID,
|
|
941
|
+
externalId: notionPage.id,
|
|
942
|
+
externalRevision: notionPage.last_edited_time,
|
|
943
|
+
externalUrl: typeof notionPage.url === "string" ? notionPage.url : void 0,
|
|
944
|
+
fetchedAt
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// src/connectors/live/gmail.ts
|
|
950
|
+
var GMAIL_CONNECTOR_ID = "gmail";
|
|
951
|
+
var GMAIL_CURSOR_KIND = "gmailWatermark";
|
|
952
|
+
var GMAIL_DEFAULT_POLL_INTERVAL_MS = 5 * 60 * 1e3;
|
|
953
|
+
var GMAIL_MAX_POLL_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
954
|
+
var MAX_TEXT_BYTES3 = 2 * 1024 * 1024;
|
|
955
|
+
var MAX_MESSAGES_PER_PASS = 200;
|
|
956
|
+
var LIST_PAGE_SIZE = 100;
|
|
957
|
+
var SEEN_IDS_MAX = 1e3;
|
|
958
|
+
var SEEN_IDS_RETAIN = 500;
|
|
959
|
+
var SKIPPED_IDS_MAX = 5e3;
|
|
960
|
+
var SKIPPED_IDS_RETAIN = 2500;
|
|
961
|
+
var GMAIL_API_BASE = "https://gmail.googleapis.com/gmail/v1";
|
|
962
|
+
var OAUTH2_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
963
|
+
function validateGmailConfig(raw) {
|
|
964
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
965
|
+
throw new TypeError(
|
|
966
|
+
`gmail: config must be an object, got ${raw === null ? "null" : Array.isArray(raw) ? "array" : typeof raw}`
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
const r = raw;
|
|
970
|
+
const clientId = requireNonEmptyString2(r.clientId, "clientId");
|
|
971
|
+
const clientSecret = requireNonEmptyString2(r.clientSecret, "clientSecret");
|
|
972
|
+
const refreshToken = requireNonEmptyString2(r.refreshToken, "refreshToken");
|
|
973
|
+
let userId = "me";
|
|
974
|
+
if (r.userId !== void 0) {
|
|
975
|
+
if (typeof r.userId !== "string") {
|
|
976
|
+
throw new TypeError(`gmail: userId must be a string (got ${typeof r.userId})`);
|
|
977
|
+
}
|
|
978
|
+
const trimmed = r.userId.trim();
|
|
979
|
+
if (trimmed.length === 0) {
|
|
980
|
+
throw new RangeError("gmail: userId must be non-empty");
|
|
981
|
+
}
|
|
982
|
+
userId = trimmed;
|
|
983
|
+
}
|
|
984
|
+
let query = "in:inbox";
|
|
985
|
+
if (r.query !== void 0) {
|
|
986
|
+
if (typeof r.query !== "string") {
|
|
987
|
+
throw new TypeError(`gmail: query must be a string (got ${typeof r.query})`);
|
|
988
|
+
}
|
|
989
|
+
query = r.query;
|
|
990
|
+
}
|
|
991
|
+
let pollIntervalMs;
|
|
992
|
+
if (r.pollIntervalMs === void 0) {
|
|
993
|
+
pollIntervalMs = GMAIL_DEFAULT_POLL_INTERVAL_MS;
|
|
994
|
+
} else if (typeof r.pollIntervalMs !== "number" || !Number.isFinite(r.pollIntervalMs)) {
|
|
995
|
+
throw new TypeError(
|
|
996
|
+
`gmail: pollIntervalMs must be a finite number (got ${JSON.stringify(r.pollIntervalMs)})`
|
|
997
|
+
);
|
|
998
|
+
} else if (!Number.isInteger(r.pollIntervalMs)) {
|
|
999
|
+
throw new TypeError(
|
|
1000
|
+
`gmail: pollIntervalMs must be an integer (got ${r.pollIntervalMs})`
|
|
1001
|
+
);
|
|
1002
|
+
} else if (r.pollIntervalMs < 1e3) {
|
|
1003
|
+
throw new RangeError(
|
|
1004
|
+
`gmail: pollIntervalMs must be \u22651000ms; got ${r.pollIntervalMs}`
|
|
1005
|
+
);
|
|
1006
|
+
} else if (r.pollIntervalMs > GMAIL_MAX_POLL_INTERVAL_MS) {
|
|
1007
|
+
throw new RangeError(
|
|
1008
|
+
`gmail: pollIntervalMs must be \u2264${GMAIL_MAX_POLL_INTERVAL_MS} (24h); got ${r.pollIntervalMs}`
|
|
1009
|
+
);
|
|
1010
|
+
} else {
|
|
1011
|
+
pollIntervalMs = r.pollIntervalMs;
|
|
1012
|
+
}
|
|
1013
|
+
return Object.freeze({
|
|
1014
|
+
clientId,
|
|
1015
|
+
clientSecret,
|
|
1016
|
+
refreshToken,
|
|
1017
|
+
userId,
|
|
1018
|
+
query,
|
|
1019
|
+
pollIntervalMs
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
function requireNonEmptyString2(value, key) {
|
|
1023
|
+
if (typeof value !== "string") {
|
|
1024
|
+
throw new TypeError(`gmail: ${key} must be a string (got ${typeof value})`);
|
|
1025
|
+
}
|
|
1026
|
+
const trimmed = value.trim();
|
|
1027
|
+
if (trimmed.length === 0) {
|
|
1028
|
+
throw new RangeError(`gmail: ${key} must be non-empty`);
|
|
1029
|
+
}
|
|
1030
|
+
return trimmed;
|
|
1031
|
+
}
|
|
1032
|
+
function isTransientGmailError(err) {
|
|
1033
|
+
return isTransientHttpError(err, ["gmailStatus"]);
|
|
1034
|
+
}
|
|
1035
|
+
function makeCursor3(payload) {
|
|
1036
|
+
return {
|
|
1037
|
+
kind: GMAIL_CURSOR_KIND,
|
|
1038
|
+
value: JSON.stringify(payload),
|
|
1039
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
function parseCursorPayload2(cursor) {
|
|
1043
|
+
if (cursor.kind !== GMAIL_CURSOR_KIND) {
|
|
1044
|
+
throw new Error(
|
|
1045
|
+
`gmail: unexpected cursor kind ${JSON.stringify(cursor.kind)}; expected ${GMAIL_CURSOR_KIND}`
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
let parsed;
|
|
1049
|
+
try {
|
|
1050
|
+
parsed = JSON.parse(cursor.value);
|
|
1051
|
+
} catch {
|
|
1052
|
+
throw new Error(`gmail: cursor value is not valid JSON`);
|
|
1053
|
+
}
|
|
1054
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1055
|
+
throw new Error(`gmail: cursor value does not match GmailCursorPayload shape`);
|
|
1056
|
+
}
|
|
1057
|
+
const p = parsed;
|
|
1058
|
+
let watermarkMs = "";
|
|
1059
|
+
if (typeof p.watermarkMs === "string" && p.watermarkMs.length > 0) {
|
|
1060
|
+
watermarkMs = p.watermarkMs;
|
|
1061
|
+
} else if (typeof p.watermarkIso === "string" && p.watermarkIso.length > 0) {
|
|
1062
|
+
const ms = new Date(p.watermarkIso).getTime();
|
|
1063
|
+
if (Number.isFinite(ms) && ms > 0) {
|
|
1064
|
+
watermarkMs = String(ms);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
let skippedIds = {};
|
|
1068
|
+
if (typeof p.skippedIds === "object" && p.skippedIds !== null && !Array.isArray(p.skippedIds)) {
|
|
1069
|
+
const raw = p.skippedIds;
|
|
1070
|
+
for (const [id, val] of Object.entries(raw)) {
|
|
1071
|
+
skippedIds[id] = typeof val === "string" ? val : "0";
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
let seenIds = {};
|
|
1075
|
+
if (typeof p.seenIds === "object" && p.seenIds !== null && !Array.isArray(p.seenIds)) {
|
|
1076
|
+
seenIds = p.seenIds;
|
|
1077
|
+
}
|
|
1078
|
+
const pageToken = typeof p.pageToken === "string" && p.pageToken.length > 0 ? p.pageToken : void 0;
|
|
1079
|
+
return { watermarkMs, skippedIds, seenIds, pageToken };
|
|
1080
|
+
}
|
|
1081
|
+
function pruneSeenIds(seenIds, watermarkMs) {
|
|
1082
|
+
const floorSecBoundaryMs = Math.floor(watermarkMs / 1e3) * 1e3;
|
|
1083
|
+
let pruned = {};
|
|
1084
|
+
for (const [id, dateMs] of Object.entries(seenIds)) {
|
|
1085
|
+
if (Number(dateMs) > floorSecBoundaryMs) {
|
|
1086
|
+
pruned[id] = dateMs;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
const entries = Object.entries(pruned);
|
|
1090
|
+
if (entries.length > SEEN_IDS_MAX) {
|
|
1091
|
+
entries.sort((a, b) => {
|
|
1092
|
+
const diff = Number(b[1]) - Number(a[1]);
|
|
1093
|
+
return diff !== 0 ? diff : a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
|
|
1094
|
+
});
|
|
1095
|
+
const retained = entries.slice(0, SEEN_IDS_RETAIN);
|
|
1096
|
+
pruned = Object.fromEntries(retained);
|
|
1097
|
+
}
|
|
1098
|
+
return pruned;
|
|
1099
|
+
}
|
|
1100
|
+
function pruneSkippedIds(skippedIds, watermarkMs) {
|
|
1101
|
+
let pruned = {};
|
|
1102
|
+
for (const [id, dateMs] of Object.entries(skippedIds)) {
|
|
1103
|
+
const ms = Number(dateMs);
|
|
1104
|
+
if (ms === 0 || ms >= watermarkMs) {
|
|
1105
|
+
pruned[id] = dateMs;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
const entries = Object.entries(pruned);
|
|
1109
|
+
if (entries.length > SKIPPED_IDS_MAX) {
|
|
1110
|
+
entries.sort((a, b) => {
|
|
1111
|
+
const diff = Number(b[1]) - Number(a[1]);
|
|
1112
|
+
return diff !== 0 ? diff : a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
|
|
1113
|
+
});
|
|
1114
|
+
const retained = entries.slice(0, SKIPPED_IDS_RETAIN);
|
|
1115
|
+
pruned = Object.fromEntries(retained);
|
|
1116
|
+
}
|
|
1117
|
+
return pruned;
|
|
1118
|
+
}
|
|
1119
|
+
function throwIfAborted3(signal) {
|
|
1120
|
+
if (signal?.aborted) {
|
|
1121
|
+
const err = new Error("gmail: sync aborted");
|
|
1122
|
+
err.name = "AbortError";
|
|
1123
|
+
throw err;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
function makeGmailApiError(status, message) {
|
|
1127
|
+
const err = new Error(`gmail: API error ${status}: ${message}`);
|
|
1128
|
+
err.gmailStatus = status;
|
|
1129
|
+
return err;
|
|
1130
|
+
}
|
|
1131
|
+
async function gmailGet(fetchFn, accessToken, path, signal) {
|
|
1132
|
+
const url = `${GMAIL_API_BASE}${path}`;
|
|
1133
|
+
const res = await fetchFn(url, {
|
|
1134
|
+
method: "GET",
|
|
1135
|
+
headers: {
|
|
1136
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1137
|
+
Accept: "application/json"
|
|
1138
|
+
},
|
|
1139
|
+
signal
|
|
1140
|
+
});
|
|
1141
|
+
const data = await res.json();
|
|
1142
|
+
if (!res.ok) {
|
|
1143
|
+
const msg = extractApiErrorMessage(data);
|
|
1144
|
+
throw makeGmailApiError(res.status, msg);
|
|
1145
|
+
}
|
|
1146
|
+
return data;
|
|
1147
|
+
}
|
|
1148
|
+
function extractApiErrorMessage(data) {
|
|
1149
|
+
if (typeof data === "object" && data !== null && typeof data.error === "object") {
|
|
1150
|
+
const errObj = data.error;
|
|
1151
|
+
if (typeof errObj.message === "string") return errObj.message;
|
|
1152
|
+
}
|
|
1153
|
+
return "unknown error";
|
|
1154
|
+
}
|
|
1155
|
+
async function exchangeRefreshToken(fetchFn, config, signal) {
|
|
1156
|
+
throwIfAborted3(signal);
|
|
1157
|
+
const body = new URLSearchParams({
|
|
1158
|
+
client_id: config.clientId,
|
|
1159
|
+
client_secret: config.clientSecret,
|
|
1160
|
+
refresh_token: config.refreshToken,
|
|
1161
|
+
grant_type: "refresh_token"
|
|
1162
|
+
});
|
|
1163
|
+
const res = await fetchFn(OAUTH2_TOKEN_URL, {
|
|
1164
|
+
method: "POST",
|
|
1165
|
+
headers: {
|
|
1166
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1167
|
+
},
|
|
1168
|
+
body: body.toString(),
|
|
1169
|
+
signal
|
|
1170
|
+
});
|
|
1171
|
+
const data = await res.json();
|
|
1172
|
+
if (!res.ok) {
|
|
1173
|
+
throw makeGmailApiError(
|
|
1174
|
+
res.status,
|
|
1175
|
+
`OAuth2 token exchange failed (HTTP ${res.status})`
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
if (typeof data !== "object" || data === null || typeof data.access_token !== "string") {
|
|
1179
|
+
throw new Error("gmail: OAuth2 token exchange returned no access_token");
|
|
1180
|
+
}
|
|
1181
|
+
return data.access_token;
|
|
1182
|
+
}
|
|
1183
|
+
function extractBodyFromPart(part) {
|
|
1184
|
+
const mime = part.mimeType ?? "";
|
|
1185
|
+
if (mime === "text/plain") {
|
|
1186
|
+
return decodeBase64urlBody(part.body?.data ?? "");
|
|
1187
|
+
}
|
|
1188
|
+
if (mime === "text/html") {
|
|
1189
|
+
const raw = decodeBase64urlBody(part.body?.data ?? "");
|
|
1190
|
+
return stripHtmlTags(raw);
|
|
1191
|
+
}
|
|
1192
|
+
if (mime.startsWith("multipart/") && Array.isArray(part.parts)) {
|
|
1193
|
+
for (const child of part.parts) {
|
|
1194
|
+
if ((child.mimeType ?? "") === "text/plain") {
|
|
1195
|
+
const text = decodeBase64urlBody(child.body?.data ?? "");
|
|
1196
|
+
if (text.length > 0) return text;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
for (const child of part.parts) {
|
|
1200
|
+
const text = extractBodyFromPart(child);
|
|
1201
|
+
if (text.length > 0) return text;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
return "";
|
|
1205
|
+
}
|
|
1206
|
+
function decodeBase64urlBody(encoded) {
|
|
1207
|
+
if (!encoded) return "";
|
|
1208
|
+
try {
|
|
1209
|
+
const base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
|
|
1210
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
1211
|
+
return Buffer.from(padded, "base64").toString("utf-8");
|
|
1212
|
+
} catch {
|
|
1213
|
+
return "";
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
function stripHtmlTags(html) {
|
|
1217
|
+
if (!html) return "";
|
|
1218
|
+
const noTags = html.replace(/<[^>]*>/g, " ");
|
|
1219
|
+
const HTML_ENTITIES = {
|
|
1220
|
+
" ": " ",
|
|
1221
|
+
"&": "&",
|
|
1222
|
+
"<": "<",
|
|
1223
|
+
">": ">",
|
|
1224
|
+
""": '"',
|
|
1225
|
+
"'": "'",
|
|
1226
|
+
"'": "'"
|
|
1227
|
+
};
|
|
1228
|
+
const decoded = noTags.replace(/&(?:#39|nbsp|amp|lt|gt|quot|apos);/gi, (entity) => {
|
|
1229
|
+
return HTML_ENTITIES[entity.toLowerCase()] ?? entity;
|
|
1230
|
+
});
|
|
1231
|
+
return decoded.replace(/\s{2,}/g, " ").trim();
|
|
1232
|
+
}
|
|
1233
|
+
function extractSubject(message) {
|
|
1234
|
+
const headers = message.payload?.headers ?? [];
|
|
1235
|
+
for (const h of headers) {
|
|
1236
|
+
if (typeof h.name === "string" && h.name.toLowerCase() === "subject") {
|
|
1237
|
+
const v = h.value;
|
|
1238
|
+
if (typeof v === "string" && v.trim().length > 0) return v.trim();
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return void 0;
|
|
1242
|
+
}
|
|
1243
|
+
function createGmailConnector(options = {}) {
|
|
1244
|
+
const fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
1245
|
+
return {
|
|
1246
|
+
id: GMAIL_CONNECTOR_ID,
|
|
1247
|
+
displayName: "Gmail",
|
|
1248
|
+
description: "Imports new inbox messages from Gmail into Remnic on a poll schedule.",
|
|
1249
|
+
validateConfig(raw) {
|
|
1250
|
+
return validateGmailConfig(raw);
|
|
1251
|
+
},
|
|
1252
|
+
async syncIncremental(args) {
|
|
1253
|
+
const config = validateGmailConfig(args.config);
|
|
1254
|
+
throwIfAborted3(args.abortSignal);
|
|
1255
|
+
const accessToken = await exchangeRefreshToken(fetchFn, config, args.abortSignal);
|
|
1256
|
+
throwIfAborted3(args.abortSignal);
|
|
1257
|
+
if (args.cursor === null) {
|
|
1258
|
+
const bootstrapResult = {
|
|
1259
|
+
newDocs: [],
|
|
1260
|
+
nextCursor: makeCursor3({
|
|
1261
|
+
watermarkMs: String(Date.now()),
|
|
1262
|
+
skippedIds: {},
|
|
1263
|
+
seenIds: {}
|
|
1264
|
+
}),
|
|
1265
|
+
skippedInaccessible: 0,
|
|
1266
|
+
skippedEmpty: 0,
|
|
1267
|
+
skippedTooLarge: 0
|
|
1268
|
+
};
|
|
1269
|
+
return bootstrapResult;
|
|
1270
|
+
}
|
|
1271
|
+
const cursorPayload = parseCursorPayload2(args.cursor);
|
|
1272
|
+
return await incrementalSync2(
|
|
1273
|
+
fetchFn,
|
|
1274
|
+
accessToken,
|
|
1275
|
+
config,
|
|
1276
|
+
cursorPayload,
|
|
1277
|
+
args.abortSignal
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
async function incrementalSync2(fetchFn, accessToken, config, cursorPayload, signal) {
|
|
1283
|
+
const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1284
|
+
const newDocs = [];
|
|
1285
|
+
let skippedInaccessible = 0;
|
|
1286
|
+
let skippedEmpty = 0;
|
|
1287
|
+
let skippedTooLarge = 0;
|
|
1288
|
+
let totalConsumed = 0;
|
|
1289
|
+
let currentWatermarkMs = 0;
|
|
1290
|
+
let afterEpochSec = 0;
|
|
1291
|
+
if (cursorPayload.watermarkMs.length > 0) {
|
|
1292
|
+
const ms = Number(cursorPayload.watermarkMs);
|
|
1293
|
+
if (Number.isFinite(ms) && ms > 0) {
|
|
1294
|
+
currentWatermarkMs = ms;
|
|
1295
|
+
afterEpochSec = Math.floor(ms / 1e3);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
const listQuery = buildListQuery(afterEpochSec, config.query);
|
|
1299
|
+
const seenIds = { ...cursorPayload.seenIds };
|
|
1300
|
+
const skippedIds = { ...cursorPayload.skippedIds };
|
|
1301
|
+
let highWaterMs = currentWatermarkMs;
|
|
1302
|
+
let pageToken = cursorPayload.pageToken;
|
|
1303
|
+
let listFullyDrained = false;
|
|
1304
|
+
let capHit = false;
|
|
1305
|
+
let capHitPageToken = void 0;
|
|
1306
|
+
while (true) {
|
|
1307
|
+
throwIfAborted3(signal);
|
|
1308
|
+
let listPath = `/users/${encodeURIComponent(config.userId)}/messages?maxResults=${LIST_PAGE_SIZE}&q=${encodeURIComponent(listQuery)}`;
|
|
1309
|
+
if (pageToken) {
|
|
1310
|
+
listPath += `&pageToken=${encodeURIComponent(pageToken)}`;
|
|
1311
|
+
}
|
|
1312
|
+
let listData;
|
|
1313
|
+
try {
|
|
1314
|
+
listData = await gmailGet(fetchFn, accessToken, listPath, signal);
|
|
1315
|
+
} catch (listErr) {
|
|
1316
|
+
const listErrObj = listErr;
|
|
1317
|
+
if (pageToken !== void 0 && listErrObj !== null && typeof listErrObj === "object" && listErrObj.gmailStatus === 400) {
|
|
1318
|
+
pageToken = void 0;
|
|
1319
|
+
listPath = `/users/${encodeURIComponent(config.userId)}/messages?maxResults=${LIST_PAGE_SIZE}&q=${encodeURIComponent(listQuery)}`;
|
|
1320
|
+
listData = await gmailGet(fetchFn, accessToken, listPath, signal);
|
|
1321
|
+
} else {
|
|
1322
|
+
throw listErr;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
throwIfAborted3(signal);
|
|
1326
|
+
const listPage = listData;
|
|
1327
|
+
const messages = listPage.messages ?? [];
|
|
1328
|
+
let capHitMidPage = false;
|
|
1329
|
+
for (const ref of messages) {
|
|
1330
|
+
throwIfAborted3(signal);
|
|
1331
|
+
if (skippedIds[ref.id]) {
|
|
1332
|
+
continue;
|
|
1333
|
+
}
|
|
1334
|
+
if (seenIds[ref.id] !== void 0) {
|
|
1335
|
+
continue;
|
|
1336
|
+
}
|
|
1337
|
+
if (totalConsumed >= MAX_MESSAGES_PER_PASS) {
|
|
1338
|
+
capHitMidPage = true;
|
|
1339
|
+
break;
|
|
1340
|
+
}
|
|
1341
|
+
totalConsumed++;
|
|
1342
|
+
const doc = await fetchMessageDocument(
|
|
1343
|
+
fetchFn,
|
|
1344
|
+
accessToken,
|
|
1345
|
+
config,
|
|
1346
|
+
ref.id,
|
|
1347
|
+
fetchedAt,
|
|
1348
|
+
signal
|
|
1349
|
+
);
|
|
1350
|
+
if (doc === "inaccessible") {
|
|
1351
|
+
skippedInaccessible++;
|
|
1352
|
+
skippedIds[ref.id] = "0";
|
|
1353
|
+
} else if (doc !== null && typeof doc === "object" && "kind" in doc) {
|
|
1354
|
+
if (doc.kind === "empty") skippedEmpty++;
|
|
1355
|
+
else skippedTooLarge++;
|
|
1356
|
+
skippedIds[ref.id] = doc.internalDate.length > 0 ? doc.internalDate : "0";
|
|
1357
|
+
const skippedMs = Number(doc.internalDate);
|
|
1358
|
+
if (Number.isFinite(skippedMs) && skippedMs > highWaterMs) {
|
|
1359
|
+
highWaterMs = skippedMs;
|
|
1360
|
+
}
|
|
1361
|
+
} else if (doc !== null) {
|
|
1362
|
+
newDocs.push(doc);
|
|
1363
|
+
const successDoc = doc;
|
|
1364
|
+
if (successDoc.source.externalRevision) {
|
|
1365
|
+
const msgMs = Number(successDoc.source.externalRevision);
|
|
1366
|
+
if (Number.isFinite(msgMs) && msgMs > highWaterMs) {
|
|
1367
|
+
highWaterMs = msgMs;
|
|
1368
|
+
}
|
|
1369
|
+
seenIds[ref.id] = successDoc.source.externalRevision;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
const hasNextPage = typeof listPage.nextPageToken === "string" && listPage.nextPageToken.length > 0;
|
|
1374
|
+
const resolvedNextPageToken = hasNextPage ? listPage.nextPageToken : void 0;
|
|
1375
|
+
if (capHitMidPage) {
|
|
1376
|
+
capHit = true;
|
|
1377
|
+
capHitPageToken = pageToken;
|
|
1378
|
+
break;
|
|
1379
|
+
}
|
|
1380
|
+
if (resolvedNextPageToken !== void 0) {
|
|
1381
|
+
pageToken = resolvedNextPageToken;
|
|
1382
|
+
continue;
|
|
1383
|
+
}
|
|
1384
|
+
listFullyDrained = true;
|
|
1385
|
+
break;
|
|
1386
|
+
}
|
|
1387
|
+
let nextWatermarkMs;
|
|
1388
|
+
let nextSeenIds;
|
|
1389
|
+
let nextSkippedIds;
|
|
1390
|
+
let nextPageToken;
|
|
1391
|
+
if (listFullyDrained && !capHit && highWaterMs > currentWatermarkMs) {
|
|
1392
|
+
nextWatermarkMs = String(highWaterMs);
|
|
1393
|
+
nextSeenIds = pruneSeenIds(seenIds, highWaterMs);
|
|
1394
|
+
nextSkippedIds = pruneSkippedIds(skippedIds, highWaterMs);
|
|
1395
|
+
nextPageToken = void 0;
|
|
1396
|
+
} else if (capHit) {
|
|
1397
|
+
nextWatermarkMs = cursorPayload.watermarkMs;
|
|
1398
|
+
nextSeenIds = pruneSeenIds(seenIds, currentWatermarkMs);
|
|
1399
|
+
nextSkippedIds = pruneSkippedIds(skippedIds, currentWatermarkMs);
|
|
1400
|
+
nextPageToken = capHitPageToken;
|
|
1401
|
+
} else {
|
|
1402
|
+
nextWatermarkMs = cursorPayload.watermarkMs;
|
|
1403
|
+
nextSeenIds = pruneSeenIds(seenIds, currentWatermarkMs);
|
|
1404
|
+
nextSkippedIds = pruneSkippedIds(skippedIds, currentWatermarkMs);
|
|
1405
|
+
nextPageToken = void 0;
|
|
1406
|
+
}
|
|
1407
|
+
const nextCursor = makeCursor3({
|
|
1408
|
+
watermarkMs: nextWatermarkMs,
|
|
1409
|
+
skippedIds: nextSkippedIds,
|
|
1410
|
+
seenIds: nextSeenIds,
|
|
1411
|
+
...nextPageToken !== void 0 ? { pageToken: nextPageToken } : {}
|
|
1412
|
+
});
|
|
1413
|
+
return {
|
|
1414
|
+
newDocs,
|
|
1415
|
+
nextCursor,
|
|
1416
|
+
skippedInaccessible,
|
|
1417
|
+
skippedEmpty,
|
|
1418
|
+
skippedTooLarge
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
function buildListQuery(afterEpochSec, userQuery) {
|
|
1422
|
+
const parts = [];
|
|
1423
|
+
if (afterEpochSec > 0) {
|
|
1424
|
+
parts.push(`after:${afterEpochSec}`);
|
|
1425
|
+
}
|
|
1426
|
+
const trimmedUser = userQuery.trim();
|
|
1427
|
+
if (trimmedUser.length > 0) {
|
|
1428
|
+
parts.push(trimmedUser);
|
|
1429
|
+
}
|
|
1430
|
+
return parts.join(" ");
|
|
1431
|
+
}
|
|
1432
|
+
async function fetchMessageDocument(fetchFn, accessToken, config, messageId, fetchedAt, signal) {
|
|
1433
|
+
throwIfAborted3(signal);
|
|
1434
|
+
let message;
|
|
1435
|
+
try {
|
|
1436
|
+
const path = `/users/${encodeURIComponent(config.userId)}/messages/${encodeURIComponent(messageId)}?format=full`;
|
|
1437
|
+
const data = await gmailGet(fetchFn, accessToken, path, signal);
|
|
1438
|
+
message = data;
|
|
1439
|
+
} catch (err) {
|
|
1440
|
+
if (isTransientGmailError(err)) {
|
|
1441
|
+
throw err;
|
|
1442
|
+
}
|
|
1443
|
+
if (err !== null && typeof err === "object" && err.gmailStatus === 401) {
|
|
1444
|
+
throw err;
|
|
1445
|
+
}
|
|
1446
|
+
return "inaccessible";
|
|
1447
|
+
}
|
|
1448
|
+
const internalDate = message.internalDate ?? "";
|
|
1449
|
+
const body = message.payload ? extractBodyFromPart(message.payload) : "";
|
|
1450
|
+
if (typeof body !== "string" || body.trim().length === 0) {
|
|
1451
|
+
return { kind: "empty", internalDate };
|
|
1452
|
+
}
|
|
1453
|
+
if (body.length > MAX_TEXT_BYTES3) {
|
|
1454
|
+
return { kind: "too-large", internalDate };
|
|
1455
|
+
}
|
|
1456
|
+
const subject = extractSubject(message);
|
|
1457
|
+
return {
|
|
1458
|
+
id: messageId,
|
|
1459
|
+
title: subject,
|
|
1460
|
+
content: body,
|
|
1461
|
+
source: {
|
|
1462
|
+
connector: GMAIL_CONNECTOR_ID,
|
|
1463
|
+
externalId: messageId,
|
|
1464
|
+
// Store internalDate (epoch ms string) as the revision so downstream
|
|
1465
|
+
// dedup can identify repeat fetches after cursor rewind.
|
|
1466
|
+
externalRevision: internalDate.length > 0 ? internalDate : void 0,
|
|
1467
|
+
fetchedAt
|
|
1468
|
+
}
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// src/connectors/live/github.ts
|
|
1473
|
+
var GITHUB_CONNECTOR_ID = "github";
|
|
1474
|
+
var GITHUB_CURSOR_KIND = "githubWatermark";
|
|
1475
|
+
var GITHUB_DEFAULT_POLL_INTERVAL_MS = 5 * 60 * 1e3;
|
|
1476
|
+
var GITHUB_MAX_POLL_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1477
|
+
var MAX_BODY_BYTES = 5 * 1024 * 1024;
|
|
1478
|
+
var MAX_ITEMS_PER_PASS = 200;
|
|
1479
|
+
var GITHUB_PAGE_SIZE = 100;
|
|
1480
|
+
var REPO_SLUG_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
1481
|
+
function validateGitHubConfig(raw) {
|
|
1482
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
1483
|
+
throw new TypeError(
|
|
1484
|
+
`github: config must be an object, got ${raw === null ? "null" : Array.isArray(raw) ? "array" : typeof raw}`
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1487
|
+
const r = raw;
|
|
1488
|
+
if (typeof r.token !== "string") {
|
|
1489
|
+
throw new TypeError(`github: token must be a string (got ${typeof r.token})`);
|
|
1490
|
+
}
|
|
1491
|
+
const token = r.token.trim();
|
|
1492
|
+
if (token.length === 0) {
|
|
1493
|
+
throw new RangeError("github: token must be non-empty");
|
|
1494
|
+
}
|
|
1495
|
+
if (typeof r.userLogin !== "string") {
|
|
1496
|
+
throw new TypeError(`github: userLogin must be a string (got ${typeof r.userLogin})`);
|
|
1497
|
+
}
|
|
1498
|
+
const userLogin = r.userLogin.trim();
|
|
1499
|
+
if (userLogin.length === 0) {
|
|
1500
|
+
throw new RangeError("github: userLogin must be non-empty");
|
|
1501
|
+
}
|
|
1502
|
+
let pollIntervalMs;
|
|
1503
|
+
if (r.pollIntervalMs === void 0) {
|
|
1504
|
+
pollIntervalMs = GITHUB_DEFAULT_POLL_INTERVAL_MS;
|
|
1505
|
+
} else if (typeof r.pollIntervalMs !== "number" || !Number.isFinite(r.pollIntervalMs)) {
|
|
1506
|
+
throw new TypeError(
|
|
1507
|
+
`github: pollIntervalMs must be a finite number (got ${JSON.stringify(r.pollIntervalMs)})`
|
|
1508
|
+
);
|
|
1509
|
+
} else if (!Number.isInteger(r.pollIntervalMs)) {
|
|
1510
|
+
throw new TypeError(`github: pollIntervalMs must be an integer (got ${r.pollIntervalMs})`);
|
|
1511
|
+
} else if (r.pollIntervalMs < 1e3) {
|
|
1512
|
+
throw new RangeError(`github: pollIntervalMs must be \u22651000ms; got ${r.pollIntervalMs}`);
|
|
1513
|
+
} else if (r.pollIntervalMs > GITHUB_MAX_POLL_INTERVAL_MS) {
|
|
1514
|
+
throw new RangeError(
|
|
1515
|
+
`github: pollIntervalMs must be \u2264${GITHUB_MAX_POLL_INTERVAL_MS} (24h); got ${r.pollIntervalMs}`
|
|
1516
|
+
);
|
|
1517
|
+
} else {
|
|
1518
|
+
pollIntervalMs = r.pollIntervalMs;
|
|
1519
|
+
}
|
|
1520
|
+
let repos = [];
|
|
1521
|
+
if (r.repos !== void 0) {
|
|
1522
|
+
if (!Array.isArray(r.repos)) {
|
|
1523
|
+
throw new TypeError(
|
|
1524
|
+
`github: repos must be an array of strings (got ${typeof r.repos})`
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1528
|
+
const out = [];
|
|
1529
|
+
for (const value of r.repos) {
|
|
1530
|
+
if (typeof value !== "string") {
|
|
1531
|
+
throw new TypeError(
|
|
1532
|
+
`github: repos entries must be strings; found ${typeof value}`
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
const trimmed = value.trim();
|
|
1536
|
+
if (!REPO_SLUG_PATTERN.test(trimmed)) {
|
|
1537
|
+
throw new RangeError(
|
|
1538
|
+
`github: repos entry ${JSON.stringify(value)} is not a valid "owner/repo" slug`
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
if (seen.has(trimmed)) continue;
|
|
1542
|
+
seen.add(trimmed);
|
|
1543
|
+
out.push(trimmed);
|
|
1544
|
+
}
|
|
1545
|
+
repos = Object.freeze(out);
|
|
1546
|
+
}
|
|
1547
|
+
let includeDiscussions = false;
|
|
1548
|
+
if (r.includeDiscussions !== void 0) {
|
|
1549
|
+
if (typeof r.includeDiscussions !== "boolean") {
|
|
1550
|
+
throw new TypeError(
|
|
1551
|
+
`github: includeDiscussions must be a boolean (got ${typeof r.includeDiscussions})`
|
|
1552
|
+
);
|
|
1553
|
+
}
|
|
1554
|
+
includeDiscussions = r.includeDiscussions;
|
|
1555
|
+
}
|
|
1556
|
+
return Object.freeze({
|
|
1557
|
+
token,
|
|
1558
|
+
userLogin,
|
|
1559
|
+
repos,
|
|
1560
|
+
pollIntervalMs,
|
|
1561
|
+
includeDiscussions
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
function makeCursor4(payload) {
|
|
1565
|
+
return {
|
|
1566
|
+
kind: GITHUB_CURSOR_KIND,
|
|
1567
|
+
value: JSON.stringify(payload),
|
|
1568
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
function parseCursorPayload3(cursor) {
|
|
1572
|
+
if (cursor.kind !== GITHUB_CURSOR_KIND) {
|
|
1573
|
+
throw new Error(
|
|
1574
|
+
`github: unexpected cursor kind ${JSON.stringify(cursor.kind)}; expected ${GITHUB_CURSOR_KIND}`
|
|
1575
|
+
);
|
|
1576
|
+
}
|
|
1577
|
+
let parsed;
|
|
1578
|
+
try {
|
|
1579
|
+
parsed = JSON.parse(cursor.value);
|
|
1580
|
+
} catch {
|
|
1581
|
+
throw new Error(`github: cursor value is not valid JSON`);
|
|
1582
|
+
}
|
|
1583
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1584
|
+
throw new Error(`github: cursor value does not match GitHubCursorPayload shape`);
|
|
1585
|
+
}
|
|
1586
|
+
const p = parsed;
|
|
1587
|
+
const watermarks = typeof p.watermarks === "object" && p.watermarks !== null && !Array.isArray(p.watermarks) ? p.watermarks : {};
|
|
1588
|
+
const seenIds = typeof p.seenIds === "object" && p.seenIds !== null && !Array.isArray(p.seenIds) ? p.seenIds : {};
|
|
1589
|
+
return { watermarks, seenIds };
|
|
1590
|
+
}
|
|
1591
|
+
function watermarkKey(repo, kind) {
|
|
1592
|
+
return `${repo}/${kind}`;
|
|
1593
|
+
}
|
|
1594
|
+
function isTransientGitHubError(err) {
|
|
1595
|
+
if (err === null || typeof err !== "object") return false;
|
|
1596
|
+
const e = err;
|
|
1597
|
+
if (typeof e.name === "string" && e.name === "AbortError") return true;
|
|
1598
|
+
const status = pickNumericGitHubStatus(e);
|
|
1599
|
+
if (status !== void 0) {
|
|
1600
|
+
if (status === 429) return true;
|
|
1601
|
+
if (status >= 500 && status <= 599) return true;
|
|
1602
|
+
return false;
|
|
1603
|
+
}
|
|
1604
|
+
const codeStr = typeof e.code === "string" ? e.code : void 0;
|
|
1605
|
+
if (codeStr !== void 0) {
|
|
1606
|
+
const transientCodes = /* @__PURE__ */ new Set([
|
|
1607
|
+
"ECONNRESET",
|
|
1608
|
+
"ECONNREFUSED",
|
|
1609
|
+
"ECONNABORTED",
|
|
1610
|
+
"ETIMEDOUT",
|
|
1611
|
+
"ESOCKETTIMEDOUT",
|
|
1612
|
+
"ENOTFOUND",
|
|
1613
|
+
"EAI_AGAIN",
|
|
1614
|
+
"EPIPE",
|
|
1615
|
+
"EHOSTUNREACH",
|
|
1616
|
+
"ENETUNREACH",
|
|
1617
|
+
"ENETDOWN",
|
|
1618
|
+
"ERR_NETWORK",
|
|
1619
|
+
"ERR_NETWORK_CHANGED"
|
|
1620
|
+
]);
|
|
1621
|
+
if (transientCodes.has(codeStr)) return true;
|
|
1622
|
+
return false;
|
|
1623
|
+
}
|
|
1624
|
+
return true;
|
|
1625
|
+
}
|
|
1626
|
+
function pickNumericGitHubStatus(e) {
|
|
1627
|
+
if (typeof e.githubStatus === "number" && Number.isFinite(e.githubStatus)) {
|
|
1628
|
+
return e.githubStatus;
|
|
1629
|
+
}
|
|
1630
|
+
if (typeof e.status === "number" && Number.isFinite(e.status)) {
|
|
1631
|
+
return e.status;
|
|
1632
|
+
}
|
|
1633
|
+
return void 0;
|
|
1634
|
+
}
|
|
1635
|
+
var GITHUB_API_BASE = "https://api.github.com";
|
|
1636
|
+
function throwIfAborted4(signal) {
|
|
1637
|
+
if (signal?.aborted) {
|
|
1638
|
+
const err = new Error("github: sync aborted");
|
|
1639
|
+
err.name = "AbortError";
|
|
1640
|
+
throw err;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
function makeGitHubApiError(status, message) {
|
|
1644
|
+
const err = new Error(`github: HTTP ${status}: ${message}`);
|
|
1645
|
+
err.githubStatus = status;
|
|
1646
|
+
return err;
|
|
1647
|
+
}
|
|
1648
|
+
async function githubGet(fetchFn, token, url, signal) {
|
|
1649
|
+
const res = await fetchFn(url, {
|
|
1650
|
+
method: "GET",
|
|
1651
|
+
headers: {
|
|
1652
|
+
Authorization: `Bearer ${token}`,
|
|
1653
|
+
"User-Agent": "remnic-connector",
|
|
1654
|
+
Accept: "application/vnd.github+json",
|
|
1655
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
1656
|
+
},
|
|
1657
|
+
signal
|
|
1658
|
+
});
|
|
1659
|
+
const data = await res.json();
|
|
1660
|
+
if (!res.ok) {
|
|
1661
|
+
const message = typeof data === "object" && data !== null && typeof data.message === "string" ? data.message : `HTTP ${res.status}`;
|
|
1662
|
+
throw makeGitHubApiError(res.status, message);
|
|
1663
|
+
}
|
|
1664
|
+
return data;
|
|
1665
|
+
}
|
|
1666
|
+
function createGitHubConnector(options = {}) {
|
|
1667
|
+
const fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
1668
|
+
return {
|
|
1669
|
+
id: GITHUB_CONNECTOR_ID,
|
|
1670
|
+
displayName: "GitHub",
|
|
1671
|
+
description: "Imports issue comments, PR review comments, and discussion posts authored by the configured user from watched repos into Remnic.",
|
|
1672
|
+
validateConfig(raw) {
|
|
1673
|
+
return validateGitHubConfig(raw);
|
|
1674
|
+
},
|
|
1675
|
+
async syncIncremental(args) {
|
|
1676
|
+
const config = validateGitHubConfig(args.config);
|
|
1677
|
+
throwIfAborted4(args.abortSignal);
|
|
1678
|
+
if (config.repos.length === 0) {
|
|
1679
|
+
const emptyPayload = { watermarks: {}, seenIds: {} };
|
|
1680
|
+
const result = {
|
|
1681
|
+
newDocs: [],
|
|
1682
|
+
nextCursor: makeCursor4(emptyPayload),
|
|
1683
|
+
skippedOtherAuthor: 0,
|
|
1684
|
+
skippedEmpty: 0,
|
|
1685
|
+
skippedTooLarge: 0
|
|
1686
|
+
};
|
|
1687
|
+
return result;
|
|
1688
|
+
}
|
|
1689
|
+
const isFirstSync = args.cursor === null;
|
|
1690
|
+
const payload = isFirstSync ? { watermarks: {}, seenIds: {} } : parseCursorPayload3(args.cursor);
|
|
1691
|
+
if (isFirstSync) {
|
|
1692
|
+
const seededPayload = await seedWatermarks(fetchFn, config, payload, args.abortSignal);
|
|
1693
|
+
return {
|
|
1694
|
+
newDocs: [],
|
|
1695
|
+
nextCursor: makeCursor4(seededPayload),
|
|
1696
|
+
skippedOtherAuthor: 0,
|
|
1697
|
+
skippedEmpty: 0,
|
|
1698
|
+
skippedTooLarge: 0
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
return await incrementalSync3(fetchFn, config, payload, args.abortSignal);
|
|
1702
|
+
}
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1705
|
+
async function seedWatermarks(fetchFn, config, initial, signal) {
|
|
1706
|
+
const watermarks = { ...initial.watermarks };
|
|
1707
|
+
for (const repo of config.repos) {
|
|
1708
|
+
throwIfAborted4(signal);
|
|
1709
|
+
try {
|
|
1710
|
+
const latest = await fetchLatestTimestamp(
|
|
1711
|
+
fetchFn,
|
|
1712
|
+
config.token,
|
|
1713
|
+
`${GITHUB_API_BASE}/repos/${repo}/issues/comments?sort=updated&direction=desc&per_page=1`,
|
|
1714
|
+
"updated_at",
|
|
1715
|
+
signal
|
|
1716
|
+
);
|
|
1717
|
+
if (latest) watermarks[watermarkKey(repo, "issue-comment")] = latest;
|
|
1718
|
+
} catch (err) {
|
|
1719
|
+
if (isTransientGitHubError(err)) throw err;
|
|
1720
|
+
}
|
|
1721
|
+
throwIfAborted4(signal);
|
|
1722
|
+
try {
|
|
1723
|
+
const latest = await fetchLatestTimestamp(
|
|
1724
|
+
fetchFn,
|
|
1725
|
+
config.token,
|
|
1726
|
+
`${GITHUB_API_BASE}/repos/${repo}/pulls/comments?sort=updated&direction=desc&per_page=1`,
|
|
1727
|
+
"updated_at",
|
|
1728
|
+
signal
|
|
1729
|
+
);
|
|
1730
|
+
if (latest) watermarks[watermarkKey(repo, "pr-review-comment")] = latest;
|
|
1731
|
+
} catch (err) {
|
|
1732
|
+
if (isTransientGitHubError(err)) throw err;
|
|
1733
|
+
}
|
|
1734
|
+
if (config.includeDiscussions) {
|
|
1735
|
+
throwIfAborted4(signal);
|
|
1736
|
+
try {
|
|
1737
|
+
const latest = await fetchLatestTimestamp(
|
|
1738
|
+
fetchFn,
|
|
1739
|
+
config.token,
|
|
1740
|
+
`${GITHUB_API_BASE}/repos/${repo}/discussions?sort=updated&direction=desc&per_page=1`,
|
|
1741
|
+
"updated_at",
|
|
1742
|
+
signal
|
|
1743
|
+
);
|
|
1744
|
+
if (latest) watermarks[watermarkKey(repo, "discussion")] = latest;
|
|
1745
|
+
} catch (err) {
|
|
1746
|
+
if (isTransientGitHubError(err)) throw err;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
return { watermarks, seenIds: {} };
|
|
1751
|
+
}
|
|
1752
|
+
async function fetchLatestTimestamp(fetchFn, token, url, field, signal) {
|
|
1753
|
+
const data = await githubGet(fetchFn, token, url, signal);
|
|
1754
|
+
if (!Array.isArray(data) || data.length === 0) return void 0;
|
|
1755
|
+
const first = data[0];
|
|
1756
|
+
if (typeof first !== "object" || first === null) return void 0;
|
|
1757
|
+
const ts = first[field];
|
|
1758
|
+
return typeof ts === "string" && ts.length > 0 ? ts : void 0;
|
|
1759
|
+
}
|
|
1760
|
+
async function incrementalSync3(fetchFn, config, payload, signal) {
|
|
1761
|
+
const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1762
|
+
const newDocs = [];
|
|
1763
|
+
const updatedWatermarks = { ...payload.watermarks };
|
|
1764
|
+
let skippedOtherAuthor = 0;
|
|
1765
|
+
let skippedEmpty = 0;
|
|
1766
|
+
let skippedTooLarge = 0;
|
|
1767
|
+
let totalConsumed = 0;
|
|
1768
|
+
const currentSeenIds = { ...payload.seenIds };
|
|
1769
|
+
const updatedSeenIds = { ...payload.seenIds };
|
|
1770
|
+
for (const repo of config.repos) {
|
|
1771
|
+
if (totalConsumed >= MAX_ITEMS_PER_PASS) break;
|
|
1772
|
+
throwIfAborted4(signal);
|
|
1773
|
+
{
|
|
1774
|
+
const wmKey = watermarkKey(repo, "issue-comment");
|
|
1775
|
+
const since = payload.watermarks[wmKey];
|
|
1776
|
+
try {
|
|
1777
|
+
const result = await fetchAndFilterComments(
|
|
1778
|
+
fetchFn,
|
|
1779
|
+
config.token,
|
|
1780
|
+
buildIssueCommentsUrl(repo, since),
|
|
1781
|
+
repo,
|
|
1782
|
+
"issue-comment",
|
|
1783
|
+
config.userLogin,
|
|
1784
|
+
since,
|
|
1785
|
+
fetchedAt,
|
|
1786
|
+
MAX_ITEMS_PER_PASS - totalConsumed,
|
|
1787
|
+
currentSeenIds,
|
|
1788
|
+
signal
|
|
1789
|
+
);
|
|
1790
|
+
for (const doc of result.docs) newDocs.push(doc);
|
|
1791
|
+
skippedOtherAuthor += result.skippedOtherAuthor;
|
|
1792
|
+
skippedEmpty += result.skippedEmpty;
|
|
1793
|
+
skippedTooLarge += result.skippedTooLarge;
|
|
1794
|
+
totalConsumed += result.consumed;
|
|
1795
|
+
if (result.latestWatermark) {
|
|
1796
|
+
const prevWm = updatedWatermarks[wmKey];
|
|
1797
|
+
const nextWm = result.latestWatermark;
|
|
1798
|
+
updatedWatermarks[wmKey] = nextWm;
|
|
1799
|
+
if (prevWm && watermarkCrossedSecond(prevWm, nextWm)) {
|
|
1800
|
+
for (const k of Object.keys(updatedSeenIds)) {
|
|
1801
|
+
if (k.startsWith(`${repo}/issue-comment/`)) delete updatedSeenIds[k];
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
for (const [k, v] of Object.entries(result.newSeenIds)) {
|
|
1805
|
+
updatedSeenIds[k] = v;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
} catch (err) {
|
|
1809
|
+
if (isTransientGitHubError(err)) throw err;
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
if (totalConsumed >= MAX_ITEMS_PER_PASS) break;
|
|
1813
|
+
throwIfAborted4(signal);
|
|
1814
|
+
{
|
|
1815
|
+
const wmKey = watermarkKey(repo, "pr-review-comment");
|
|
1816
|
+
const since = payload.watermarks[wmKey];
|
|
1817
|
+
try {
|
|
1818
|
+
const result = await fetchAndFilterComments(
|
|
1819
|
+
fetchFn,
|
|
1820
|
+
config.token,
|
|
1821
|
+
buildPrReviewCommentsUrl(repo, since),
|
|
1822
|
+
repo,
|
|
1823
|
+
"pr-review-comment",
|
|
1824
|
+
config.userLogin,
|
|
1825
|
+
since,
|
|
1826
|
+
fetchedAt,
|
|
1827
|
+
MAX_ITEMS_PER_PASS - totalConsumed,
|
|
1828
|
+
currentSeenIds,
|
|
1829
|
+
signal
|
|
1830
|
+
);
|
|
1831
|
+
for (const doc of result.docs) newDocs.push(doc);
|
|
1832
|
+
skippedOtherAuthor += result.skippedOtherAuthor;
|
|
1833
|
+
skippedEmpty += result.skippedEmpty;
|
|
1834
|
+
skippedTooLarge += result.skippedTooLarge;
|
|
1835
|
+
totalConsumed += result.consumed;
|
|
1836
|
+
if (result.latestWatermark) {
|
|
1837
|
+
const prevWm = updatedWatermarks[wmKey];
|
|
1838
|
+
const nextWm = result.latestWatermark;
|
|
1839
|
+
updatedWatermarks[wmKey] = nextWm;
|
|
1840
|
+
if (prevWm && watermarkCrossedSecond(prevWm, nextWm)) {
|
|
1841
|
+
for (const k of Object.keys(updatedSeenIds)) {
|
|
1842
|
+
if (k.startsWith(`${repo}/pr-review-comment/`)) delete updatedSeenIds[k];
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
for (const [k, v] of Object.entries(result.newSeenIds)) {
|
|
1846
|
+
updatedSeenIds[k] = v;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
} catch (err) {
|
|
1850
|
+
if (isTransientGitHubError(err)) throw err;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
if (config.includeDiscussions && totalConsumed < MAX_ITEMS_PER_PASS) {
|
|
1854
|
+
throwIfAborted4(signal);
|
|
1855
|
+
const wmKey = watermarkKey(repo, "discussion");
|
|
1856
|
+
const since = payload.watermarks[wmKey];
|
|
1857
|
+
try {
|
|
1858
|
+
const result = await fetchAndFilterComments(
|
|
1859
|
+
fetchFn,
|
|
1860
|
+
config.token,
|
|
1861
|
+
buildDiscussionsUrl(repo, since),
|
|
1862
|
+
repo,
|
|
1863
|
+
"discussion",
|
|
1864
|
+
config.userLogin,
|
|
1865
|
+
since,
|
|
1866
|
+
fetchedAt,
|
|
1867
|
+
MAX_ITEMS_PER_PASS - totalConsumed,
|
|
1868
|
+
currentSeenIds,
|
|
1869
|
+
signal
|
|
1870
|
+
);
|
|
1871
|
+
for (const doc of result.docs) newDocs.push(doc);
|
|
1872
|
+
skippedOtherAuthor += result.skippedOtherAuthor;
|
|
1873
|
+
skippedEmpty += result.skippedEmpty;
|
|
1874
|
+
skippedTooLarge += result.skippedTooLarge;
|
|
1875
|
+
totalConsumed += result.consumed;
|
|
1876
|
+
if (result.latestWatermark) {
|
|
1877
|
+
const prevWm = updatedWatermarks[wmKey];
|
|
1878
|
+
const nextWm = result.latestWatermark;
|
|
1879
|
+
updatedWatermarks[wmKey] = nextWm;
|
|
1880
|
+
if (prevWm && watermarkCrossedSecond(prevWm, nextWm)) {
|
|
1881
|
+
for (const k of Object.keys(updatedSeenIds)) {
|
|
1882
|
+
if (k.startsWith(`${repo}/discussion/`)) delete updatedSeenIds[k];
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
for (const [k, v] of Object.entries(result.newSeenIds)) {
|
|
1886
|
+
updatedSeenIds[k] = v;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
} catch (err) {
|
|
1890
|
+
if (isTransientGitHubError(err)) throw err;
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
return {
|
|
1895
|
+
newDocs,
|
|
1896
|
+
nextCursor: makeCursor4({ watermarks: updatedWatermarks, seenIds: updatedSeenIds }),
|
|
1897
|
+
skippedOtherAuthor,
|
|
1898
|
+
skippedEmpty,
|
|
1899
|
+
skippedTooLarge
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
function watermarkCrossedSecond(prev, next) {
|
|
1903
|
+
return prev.slice(0, 19) < next.slice(0, 19);
|
|
1904
|
+
}
|
|
1905
|
+
function buildIssueCommentsUrl(repo, since) {
|
|
1906
|
+
const base = `${GITHUB_API_BASE}/repos/${repo}/issues/comments?sort=updated&direction=asc&per_page=${GITHUB_PAGE_SIZE}`;
|
|
1907
|
+
return since ? `${base}&since=${encodeURIComponent(since)}` : base;
|
|
1908
|
+
}
|
|
1909
|
+
function buildPrReviewCommentsUrl(repo, since) {
|
|
1910
|
+
const base = `${GITHUB_API_BASE}/repos/${repo}/pulls/comments?sort=updated&direction=asc&per_page=${GITHUB_PAGE_SIZE}`;
|
|
1911
|
+
return since ? `${base}&since=${encodeURIComponent(since)}` : base;
|
|
1912
|
+
}
|
|
1913
|
+
function buildDiscussionsUrl(repo, since) {
|
|
1914
|
+
const base = `${GITHUB_API_BASE}/repos/${repo}/discussions?sort=updated&direction=asc&per_page=${GITHUB_PAGE_SIZE}`;
|
|
1915
|
+
return since ? `${base}&since=${encodeURIComponent(since)}` : base;
|
|
1916
|
+
}
|
|
1917
|
+
async function fetchAndFilterComments(fetchFn, token, firstPageUrl, repo, kind, userLogin, since, fetchedAt, remainingBudget, seenIds, signal) {
|
|
1918
|
+
const docs = [];
|
|
1919
|
+
let skippedOtherAuthor = 0;
|
|
1920
|
+
let skippedEmpty = 0;
|
|
1921
|
+
let skippedTooLarge = 0;
|
|
1922
|
+
let consumed = 0;
|
|
1923
|
+
let latestWatermark = void 0;
|
|
1924
|
+
const newSeenIds = {};
|
|
1925
|
+
let nextUrl = firstPageUrl;
|
|
1926
|
+
while (nextUrl && consumed < remainingBudget) {
|
|
1927
|
+
throwIfAborted4(signal);
|
|
1928
|
+
const data = await githubGet(fetchFn, token, nextUrl, signal);
|
|
1929
|
+
if (!Array.isArray(data)) break;
|
|
1930
|
+
for (const item of data) {
|
|
1931
|
+
if (consumed >= remainingBudget) break;
|
|
1932
|
+
throwIfAborted4(signal);
|
|
1933
|
+
const comment = item;
|
|
1934
|
+
if (since && comment.updated_at < since) {
|
|
1935
|
+
continue;
|
|
1936
|
+
}
|
|
1937
|
+
const seenKey = `${repo}/${kind}/${comment.id}`;
|
|
1938
|
+
if (seenIds[seenKey] === comment.updated_at) {
|
|
1939
|
+
continue;
|
|
1940
|
+
}
|
|
1941
|
+
const authorLogin = comment.user?.login ?? null;
|
|
1942
|
+
if (authorLogin !== userLogin) {
|
|
1943
|
+
skippedOtherAuthor++;
|
|
1944
|
+
if (!latestWatermark || comment.updated_at > latestWatermark) {
|
|
1945
|
+
latestWatermark = comment.updated_at;
|
|
1946
|
+
}
|
|
1947
|
+
continue;
|
|
1948
|
+
}
|
|
1949
|
+
const body = comment.body ?? "";
|
|
1950
|
+
const trimmed = body.trim();
|
|
1951
|
+
if (trimmed.length === 0) {
|
|
1952
|
+
skippedEmpty++;
|
|
1953
|
+
if (!latestWatermark || comment.updated_at > latestWatermark) {
|
|
1954
|
+
latestWatermark = comment.updated_at;
|
|
1955
|
+
}
|
|
1956
|
+
continue;
|
|
1957
|
+
}
|
|
1958
|
+
if (trimmed.length > MAX_BODY_BYTES) {
|
|
1959
|
+
skippedTooLarge++;
|
|
1960
|
+
if (!latestWatermark || comment.updated_at > latestWatermark) {
|
|
1961
|
+
latestWatermark = comment.updated_at;
|
|
1962
|
+
}
|
|
1963
|
+
continue;
|
|
1964
|
+
}
|
|
1965
|
+
consumed++;
|
|
1966
|
+
const doc = buildDocument(comment, repo, kind, fetchedAt);
|
|
1967
|
+
docs.push(doc);
|
|
1968
|
+
if (!latestWatermark || comment.updated_at > latestWatermark) {
|
|
1969
|
+
latestWatermark = comment.updated_at;
|
|
1970
|
+
}
|
|
1971
|
+
newSeenIds[seenKey] = comment.updated_at;
|
|
1972
|
+
}
|
|
1973
|
+
if (data.length < GITHUB_PAGE_SIZE) {
|
|
1974
|
+
nextUrl = void 0;
|
|
1975
|
+
} else {
|
|
1976
|
+
nextUrl = advancePageUrl(nextUrl);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
return { docs, skippedOtherAuthor, skippedEmpty, skippedTooLarge, consumed, latestWatermark, newSeenIds };
|
|
1980
|
+
}
|
|
1981
|
+
function advancePageUrl(url) {
|
|
1982
|
+
try {
|
|
1983
|
+
const u = new URL(url);
|
|
1984
|
+
const page = parseInt(u.searchParams.get("page") ?? "1", 10);
|
|
1985
|
+
u.searchParams.set("page", String(isNaN(page) ? 2 : page + 1));
|
|
1986
|
+
return u.toString();
|
|
1987
|
+
} catch {
|
|
1988
|
+
return "";
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
function buildDocument(comment, repo, kind, fetchedAt) {
|
|
1992
|
+
const externalId = `${repo}/${kind}/${comment.id}`;
|
|
1993
|
+
const externalUrl = typeof comment.html_url === "string" && comment.html_url.length > 0 ? comment.html_url : void 0;
|
|
1994
|
+
const title = buildTitle(repo, kind, comment);
|
|
1995
|
+
return {
|
|
1996
|
+
id: externalId,
|
|
1997
|
+
title,
|
|
1998
|
+
content: (comment.body ?? "").trim(),
|
|
1999
|
+
source: {
|
|
2000
|
+
connector: GITHUB_CONNECTOR_ID,
|
|
2001
|
+
externalId,
|
|
2002
|
+
externalRevision: comment.updated_at,
|
|
2003
|
+
externalUrl,
|
|
2004
|
+
fetchedAt
|
|
2005
|
+
}
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
function buildTitle(repo, kind, comment) {
|
|
2009
|
+
const kindLabel = kind === "issue-comment" ? "Issue comment" : kind === "pr-review-comment" ? "PR review comment" : "Discussion comment";
|
|
2010
|
+
return `${kindLabel} in ${repo} (#${comment.id})`;
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
export {
|
|
2014
|
+
LiveConnectorRegistryError,
|
|
2015
|
+
LiveConnectorRegistry,
|
|
2016
|
+
GOOGLE_DRIVE_CONNECTOR_ID,
|
|
2017
|
+
GOOGLE_DRIVE_CURSOR_KIND,
|
|
2018
|
+
DEFAULT_POLL_INTERVAL_MS,
|
|
2019
|
+
validateGoogleDriveConfig,
|
|
2020
|
+
createGoogleDriveConnector,
|
|
2021
|
+
defaultGoogleDriveClientFactory,
|
|
2022
|
+
NOTION_CONNECTOR_ID,
|
|
2023
|
+
NOTION_CURSOR_KIND,
|
|
2024
|
+
NOTION_DEFAULT_POLL_INTERVAL_MS,
|
|
2025
|
+
validateNotionConfig,
|
|
2026
|
+
createNotionConnector,
|
|
2027
|
+
GMAIL_CONNECTOR_ID,
|
|
2028
|
+
GMAIL_DEFAULT_POLL_INTERVAL_MS,
|
|
2029
|
+
validateGmailConfig,
|
|
2030
|
+
createGmailConnector,
|
|
2031
|
+
GITHUB_CONNECTOR_ID,
|
|
2032
|
+
GITHUB_DEFAULT_POLL_INTERVAL_MS,
|
|
2033
|
+
validateGitHubConfig,
|
|
2034
|
+
createGitHubConnector
|
|
2035
|
+
};
|
|
2036
|
+
//# sourceMappingURL=chunk-L2IO2QPY.js.map
|