@cursor/bdk 0.2.3 → 0.2.5
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/README.md +76 -1154
- package/dist/bin/agent-serve.js +9 -1
- package/dist/channels/slack/attachments.d.ts +2 -1
- package/dist/channels/slack/attachments.d.ts.map +1 -1
- package/dist/channels/slack/attachments.js +19 -9
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +26 -24
- package/dist/channels/slack/thread-context.d.ts +21 -11
- package/dist/channels/slack/thread-context.d.ts.map +1 -1
- package/dist/channels/slack/thread-context.js +75 -27
- package/dist/docs/404.html +2 -2
- package/dist/docs/assets/{app.BOIHhrKi.js → app.94Iu5dyD.js} +4 -4
- package/dist/docs/assets/{building-with-agents.md.-fcjCmJU.js → building-with-agents.md.BPjPFxFg.js} +2 -2
- package/dist/docs/assets/{building-with-agents.md.-fcjCmJU.lean.js → building-with-agents.md.BPjPFxFg.lean.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BITLoOQa.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.D49zVZqM.js → VPLocalSearchBox.DTKFDlBr.js} +1 -1
- package/dist/docs/assets/chunks/{arc.hmjb-sc4.js → arc.DuzwHEeg.js} +1 -1
- package/dist/docs/assets/chunks/{architectureDiagram-Q4EWVU46.CxEI1KqM.js → architectureDiagram-Q4EWVU46.C6lyhLs0.js} +1 -1
- package/dist/docs/assets/chunks/{baseUniq.DHHvBWoV.js → baseUniq.DUNEWlY8.js} +1 -1
- package/dist/docs/assets/chunks/{blockDiagram-DXYQGD6D.DnXW-3bu.js → blockDiagram-DXYQGD6D.CXOQGI0O.js} +1 -1
- package/dist/docs/assets/chunks/{c4Diagram-AHTNJAMY.e-47O2jM.js → c4Diagram-AHTNJAMY.BYdPE-Oe.js} +1 -1
- package/dist/docs/assets/chunks/channel.B46uXH41.js +1 -0
- package/dist/docs/assets/chunks/{chunk-4BX2VUAB.3ry4pYXB.js → chunk-4BX2VUAB.YIV9HeX0.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-4TB4RGXK.DGIZFRx6.js → chunk-4TB4RGXK.BG80DEOc.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-55IACEB6.DKBnqg9U.js → chunk-55IACEB6.BDKJSZQc.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-EDXVE4YY.BIU8Ue5k.js → chunk-EDXVE4YY.CtYnQqts.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-FMBD7UC4.DP-TFhjI.js → chunk-FMBD7UC4.CityOth-.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-OYMX7WX6.Ux0OYj5v.js → chunk-OYMX7WX6.CMD22CQX.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-QZHKN3VN.BzHMwYox.js → chunk-QZHKN3VN.WWpc3ND0.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-YZCP3GAM.D4OGAsSP.js → chunk-YZCP3GAM.BMg-yhj3.js} +1 -1
- package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.0cmCFsSf.js +1 -0
- package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.0cmCFsSf.js +1 -0
- package/dist/docs/assets/chunks/clone.1vTOFZT1.js +1 -0
- package/dist/docs/assets/chunks/{cose-bilkent-S5V4N54A.Jq_7BUNx.js → cose-bilkent-S5V4N54A.Buu_LC5q.js} +1 -1
- package/dist/docs/assets/chunks/{dagre-KV5264BT.Di-skR2x.js → dagre-KV5264BT.DW7onj6E.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-5BDNPKRD.DeREHIdH.js → diagram-5BDNPKRD.B3NxIbEV.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-G4DWMVQ6.C7Kcqxro.js → diagram-G4DWMVQ6.BJvN6Vkv.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-MMDJMWI5.BK3eohyo.js → diagram-MMDJMWI5.wfRWrexs.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-TYMM5635.C6TyymXT.js → diagram-TYMM5635.7QtjSYio.js} +1 -1
- package/dist/docs/assets/chunks/{erDiagram-SMLLAGMA.DYGG-gGx.js → erDiagram-SMLLAGMA.jxPgDH1t.js} +1 -1
- package/dist/docs/assets/chunks/{flowDiagram-DWJPFMVM.C9nBcSV4.js → flowDiagram-DWJPFMVM.BhDWA-TO.js} +1 -1
- package/dist/docs/assets/chunks/{ganttDiagram-T4ZO3ILL.wAg_ymsp.js → ganttDiagram-T4ZO3ILL.Ijq8iYKv.js} +1 -1
- package/dist/docs/assets/chunks/{gitGraphDiagram-UUTBAWPF.B87j9Oeg.js → gitGraphDiagram-UUTBAWPF.DXpfW4YG.js} +1 -1
- package/dist/docs/assets/chunks/{graph.CuFMj31w.js → graph.CoPlssRl.js} +1 -1
- package/dist/docs/assets/chunks/{infoDiagram-42DDH7IO.P_mLGumP.js → infoDiagram-42DDH7IO.eJuv_EOz.js} +1 -1
- package/dist/docs/assets/chunks/{ishikawaDiagram-UXIWVN3A.DyuL_MsT.js → ishikawaDiagram-UXIWVN3A._OEzenCR.js} +1 -1
- package/dist/docs/assets/chunks/{journeyDiagram-VCZTEJTY.CtP09IoJ.js → journeyDiagram-VCZTEJTY.CwlTjirB.js} +1 -1
- package/dist/docs/assets/chunks/{kanban-definition-6JOO6SKY.BoeSYPZo.js → kanban-definition-6JOO6SKY.DSp_C8cZ.js} +1 -1
- package/dist/docs/assets/chunks/{layout.B0lMMtNo.js → layout.Bms9s7uB.js} +1 -1
- package/dist/docs/assets/chunks/{linear.HxRBiAFX.js → linear.BjnmB4je.js} +1 -1
- package/dist/docs/assets/chunks/{min.BGlFFHwS.js → min.CKkYlMsn.js} +1 -1
- package/dist/docs/assets/chunks/{mindmap-definition-QFDTVHPH.JyLBD9M8.js → mindmap-definition-QFDTVHPH.T2d0vJ7X.js} +1 -1
- package/dist/docs/assets/chunks/{pieDiagram-DEJITSTG.DnSmNZF8.js → pieDiagram-DEJITSTG.DU0RoCvx.js} +1 -1
- package/dist/docs/assets/chunks/{quadrantDiagram-34T5L4WZ.CUCnbfCa.js → quadrantDiagram-34T5L4WZ.9QZjM-x0.js} +1 -1
- package/dist/docs/assets/chunks/{requirementDiagram-MS252O5E.py0IVUn5.js → requirementDiagram-MS252O5E.CZpylJMd.js} +1 -1
- package/dist/docs/assets/chunks/{sankeyDiagram-XADWPNL6.Bkc-HWYp.js → sankeyDiagram-XADWPNL6.CnFdfW95.js} +1 -1
- package/dist/docs/assets/chunks/{sequenceDiagram-FGHM5R23.Dqn8Mlv2.js → sequenceDiagram-FGHM5R23.-jC0PP1t.js} +1 -1
- package/dist/docs/assets/chunks/{stateDiagram-FHFEXIEX.JikkoQUU.js → stateDiagram-FHFEXIEX.DG7yBRUU.js} +1 -1
- package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.CbsZcOOu.js +1 -0
- package/dist/docs/assets/chunks/{theme.b6HQ6zG_.js → theme.B3O9koXV.js} +2 -2
- package/dist/docs/assets/chunks/{timeline-definition-GMOUNBTQ.BvpSXiBj.js → timeline-definition-GMOUNBTQ.Bf9u4Ebb.js} +1 -1
- package/dist/docs/assets/chunks/{vennDiagram-DHZGUBPP.CU7MVnZx.js → vennDiagram-DHZGUBPP.BjhbbYE6.js} +1 -1
- package/dist/docs/assets/chunks/{wardley-RL74JXVD.BKwC4ctG.js → wardley-RL74JXVD.DEA21akR.js} +1 -1
- package/dist/docs/assets/chunks/{wardleyDiagram-NUSXRM2D.BfaF6Uhe.js → wardleyDiagram-NUSXRM2D.8fRb4Nb5.js} +1 -1
- package/dist/docs/assets/chunks/{xychartDiagram-5P7HB3ND.Bg5dcpj4.js → xychartDiagram-5P7HB3ND.DoOP3zZM.js} +1 -1
- package/dist/docs/assets/{deployment.md.BJ8i7Q6p.js → deployment.md.BdqTiGdc.js} +5 -5
- package/dist/docs/assets/{deployment.md.BJ8i7Q6p.lean.js → deployment.md.BdqTiGdc.lean.js} +1 -1
- package/dist/docs/assets/{evals.md.CCAtNcuL.js → evals.md.LRCSn6Ik.js} +2 -2
- package/dist/docs/assets/{evals.md.CCAtNcuL.lean.js → evals.md.LRCSn6Ik.lean.js} +1 -1
- package/dist/docs/assets/{guides_agent-to-agent.md.CGkicXeB.js → guides_agent-to-agent.md.Cps5NiCV.js} +2 -2
- package/dist/docs/assets/{guides_agent-to-agent.md.CGkicXeB.lean.js → guides_agent-to-agent.md.Cps5NiCV.lean.js} +1 -1
- package/dist/docs/assets/{guides_bitbucket.md.Cb3LPKt3.js → guides_bitbucket.md.BB6fqD-M.js} +2 -2
- package/dist/docs/assets/{guides_bitbucket.md.Cb3LPKt3.lean.js → guides_bitbucket.md.BB6fqD-M.lean.js} +1 -1
- package/dist/docs/assets/{guides_cloud-agents.md.BuEkDCwo.js → guides_cloud-agents.md.CWJjS9No.js} +2 -2
- package/dist/docs/assets/{guides_cloud-agents.md.BuEkDCwo.lean.js → guides_cloud-agents.md.CWJjS9No.lean.js} +1 -1
- package/dist/docs/assets/{guides_convert-automation.md.Ci-Y2iD5.js → guides_convert-automation.md.DMEX9nF-.js} +3 -3
- package/dist/docs/assets/{guides_convert-automation.md.Ci-Y2iD5.lean.js → guides_convert-automation.md.DMEX9nF-.lean.js} +1 -1
- package/dist/docs/assets/{guides_github.md.Bq9kQU9B.js → guides_github.md.Cr1Xlf-m.js} +3 -3
- package/dist/docs/assets/{guides_github.md.Bq9kQU9B.lean.js → guides_github.md.Cr1Xlf-m.lean.js} +1 -1
- package/dist/docs/assets/{guides_gitlab.md.BM27P43u.js → guides_gitlab.md.CMpmFi4M.js} +2 -2
- package/dist/docs/assets/{guides_gitlab.md.BM27P43u.lean.js → guides_gitlab.md.CMpmFi4M.lean.js} +1 -1
- package/dist/docs/assets/{guides_grokbot-agents.md.yxVbSlKA.js → guides_grokbot-agents.md.tD1smFhU.js} +2 -2
- package/dist/docs/assets/{guides_grokbot-agents.md.yxVbSlKA.lean.js → guides_grokbot-agents.md.tD1smFhU.lean.js} +1 -1
- package/dist/docs/assets/{guides_hooks.md.BWoiNaMq.js → guides_hooks.md.5Ce3NXxZ.js} +2 -2
- package/dist/docs/assets/{guides_hooks.md.BWoiNaMq.lean.js → guides_hooks.md.5Ce3NXxZ.lean.js} +1 -1
- package/dist/docs/assets/{guides_improve.md.COtL-6ft.js → guides_improve.md.CRrYq22s.js} +2 -2
- package/dist/docs/assets/{guides_improve.md.COtL-6ft.lean.js → guides_improve.md.CRrYq22s.lean.js} +1 -1
- package/dist/docs/assets/{guides_jev.md.BNDm14Gy.js → guides_jev.md.D7XH4JH3.js} +2 -2
- package/dist/docs/assets/{guides_mcp-oauth.md.Cu6mhZ_3.js → guides_mcp-oauth.md.DR3zhC9r.js} +2 -2
- package/dist/docs/assets/{guides_mcp-oauth.md.Cu6mhZ_3.lean.js → guides_mcp-oauth.md.DR3zhC9r.lean.js} +1 -1
- package/dist/docs/assets/{guides_opentelemetry.md.DLlIB5rv.js → guides_opentelemetry.md.jcPNrQbH.js} +8 -6
- package/dist/docs/assets/{guides_opentelemetry.md.DLlIB5rv.lean.js → guides_opentelemetry.md.jcPNrQbH.lean.js} +1 -1
- package/dist/docs/assets/{guides_slack.md.diqqXTTm.js → guides_slack.md.Bn-WupSE.js} +8 -8
- package/dist/docs/assets/{guides_slack.md.diqqXTTm.lean.js → guides_slack.md.Bn-WupSE.lean.js} +1 -1
- package/dist/docs/assets/{guides_webhooks.md.D0furjw0.js → guides_webhooks.md.BBvAm_VF.js} +2 -2
- package/dist/docs/assets/{guides_webhooks.md.D0furjw0.lean.js → guides_webhooks.md.BBvAm_VF.lean.js} +1 -1
- package/dist/docs/assets/{hillclimbing.md.Biy0m-Qb.js → hillclimbing.md.Dxvd-0gT.js} +2 -2
- package/dist/docs/assets/{hillclimbing.md.Biy0m-Qb.lean.js → hillclimbing.md.Dxvd-0gT.lean.js} +1 -1
- package/dist/docs/assets/index.md.Dxxr7pFa.js +27 -0
- package/dist/docs/assets/{index.md.CVc_oOA3.lean.js → index.md.Dxxr7pFa.lean.js} +1 -1
- package/dist/docs/assets/{quickstart.md.CHpHPBUN.js → quickstart.md.BK5gjHM2.js} +2 -2
- package/dist/docs/assets/{quickstart.md.CHpHPBUN.lean.js → quickstart.md.BK5gjHM2.lean.js} +1 -1
- package/dist/docs/assets/{reference_agent-config.md.DzGI777n.js → reference_agent-config.md.DrtLxWgR.js} +4 -4
- package/dist/docs/assets/{reference_agent-config.md.DzGI777n.lean.js → reference_agent-config.md.DrtLxWgR.lean.js} +1 -1
- package/dist/docs/assets/{reference_artifacts.md.CQfKq8F2.js → reference_artifacts.md.DLV9MZES.js} +2 -2
- package/dist/docs/assets/{reference_artifacts.md.CQfKq8F2.lean.js → reference_artifacts.md.DLV9MZES.lean.js} +1 -1
- package/dist/docs/assets/{reference_channels.md.mbNtKCiR.js → reference_channels.md.LbNIJBwd.js} +2 -2
- package/dist/docs/assets/{reference_channels.md.mbNtKCiR.lean.js → reference_channels.md.LbNIJBwd.lean.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.DgeLTO18.js → reference_cli.md.BsgDqYPh.js} +4 -4
- package/dist/docs/assets/{reference_cli.md.DgeLTO18.lean.js → reference_cli.md.BsgDqYPh.lean.js} +1 -1
- package/dist/docs/assets/{reference_connections.md.COq--gAm.js → reference_connections.md.Bljpve-y.js} +4 -4
- package/dist/docs/assets/{reference_connections.md.COq--gAm.lean.js → reference_connections.md.Bljpve-y.lean.js} +1 -1
- package/dist/docs/assets/{reference_evals.md.CJm3uCUm.js → reference_evals.md.D5i8PuXJ.js} +2 -2
- package/dist/docs/assets/{reference_evals.md.CJm3uCUm.lean.js → reference_evals.md.D5i8PuXJ.lean.js} +1 -1
- package/dist/docs/assets/{reference_extensions.md.CPPrkWlJ.js → reference_extensions.md.OUacPetl.js} +5 -4
- package/dist/docs/assets/{reference_extensions.md.CPPrkWlJ.lean.js → reference_extensions.md.OUacPetl.lean.js} +1 -1
- package/dist/docs/assets/{reference_hooks.md.DbthhtIR.js → reference_hooks.md.C5LCJ1R3.js} +2 -2
- package/dist/docs/assets/{reference_hooks.md.DbthhtIR.lean.js → reference_hooks.md.C5LCJ1R3.lean.js} +1 -1
- package/dist/docs/assets/{reference_http-api.md.BZOvhCqY.js → reference_http-api.md.DbFNOubV.js} +1 -1
- package/dist/docs/assets/{reference_instructions.md.Bdwscs4G.js → reference_instructions.md.DrcHZXig.js} +2 -2
- package/dist/docs/assets/{reference_instructions.md.Bdwscs4G.lean.js → reference_instructions.md.DrcHZXig.lean.js} +1 -1
- package/dist/docs/assets/reference_playground.md.1hx0gcIu.js +1 -0
- package/dist/docs/assets/{reference_playground.md.BdQzRJc2.lean.js → reference_playground.md.1hx0gcIu.lean.js} +1 -1
- package/dist/docs/assets/{reference_project-layout.md.CpLQ9j4K.js → reference_project-layout.md.C2JMRh7p.js} +2 -2
- package/dist/docs/assets/{reference_prompt.md.BQZB4eaV.js → reference_prompt.md.DqPh71XN.js} +3 -3
- package/dist/docs/assets/{reference_prompt.md.BQZB4eaV.lean.js → reference_prompt.md.DqPh71XN.lean.js} +1 -1
- package/dist/docs/assets/{reference_schedules.md.BVU5EFbz.js → reference_schedules.md.BuJ8fgMa.js} +2 -2
- package/dist/docs/assets/{reference_schedules.md.BVU5EFbz.lean.js → reference_schedules.md.BuJ8fgMa.lean.js} +1 -1
- package/dist/docs/assets/reference_sessions.md.CM5zo3w4.js +1 -0
- package/dist/docs/assets/{reference_sessions.md.DeRUItHD.lean.js → reference_sessions.md.CM5zo3w4.lean.js} +1 -1
- package/dist/docs/assets/{reference_skills.md.Cy00QMKS.js → reference_skills.md.C0guGcH0.js} +4 -4
- package/dist/docs/assets/{reference_skills.md.Cy00QMKS.lean.js → reference_skills.md.C0guGcH0.lean.js} +1 -1
- package/dist/docs/assets/{reference_subagents.md.B8oyeXOI.js → reference_subagents.md.gmPt5J-M.js} +2 -2
- package/dist/docs/assets/{reference_subagents.md.B8oyeXOI.lean.js → reference_subagents.md.gmPt5J-M.lean.js} +1 -1
- package/dist/docs/assets/{reference_tools.md.hg2Wi5Bq.js → reference_tools.md.v3SXVSs_.js} +2 -2
- package/dist/docs/assets/{reference_tools.md.hg2Wi5Bq.lean.js → reference_tools.md.v3SXVSs_.lean.js} +1 -1
- package/dist/docs/assets/{templates_agentic-owners.md.BykUiZ8j.js → templates_agentic-owners.md.vtjfwxbf.js} +2 -2
- package/dist/docs/assets/{templates_agentic-owners.md.BykUiZ8j.lean.js → templates_agentic-owners.md.vtjfwxbf.lean.js} +1 -1
- package/dist/docs/assets/templates_grokbot-agents.md.2sydjFiO.js +5 -0
- package/dist/docs/assets/templates_grokbot-agents.md.2sydjFiO.lean.js +1 -0
- package/dist/docs/assets/{templates_security-reviewer.md.DXMmKl_o.js → templates_security-reviewer.md.CX9l9bcg.js} +2 -2
- package/dist/docs/assets/{templates_security-reviewer.md.DXMmKl_o.lean.js → templates_security-reviewer.md.CX9l9bcg.lean.js} +1 -1
- package/dist/docs/assets/{templates_thermo-quality-review.md.Dg1p1MsG.js → templates_thermo-quality-review.md.95qmTmr1.js} +2 -2
- package/dist/docs/assets/{templates_thermo-quality-review.md.Dg1p1MsG.lean.js → templates_thermo-quality-review.md.95qmTmr1.lean.js} +1 -1
- package/dist/docs/assets/{templates_thermo-review.md.Dlylkowj.js → templates_thermo-review.md.DQh6Q0C-.js} +2 -2
- package/dist/docs/assets/{templates_thermo-review.md.Dlylkowj.lean.js → templates_thermo-review.md.DQh6Q0C-.lean.js} +1 -1
- package/dist/docs/assets/{templates_triage.md.CvJyx81N.js → templates_triage.md.BFlQhWXf.js} +3 -3
- package/dist/docs/assets/{templates_triage.md.CvJyx81N.lean.js → templates_triage.md.BFlQhWXf.lean.js} +1 -1
- package/dist/docs/assets/troubleshooting.md.Bscj6ODe.js +1 -0
- package/dist/docs/assets/{troubleshooting.md.Cw5PKa3t.lean.js → troubleshooting.md.Bscj6ODe.lean.js} +1 -1
- package/dist/docs/building-with-agents.html +36 -36
- package/dist/docs/building-with-agents.md +0 -3
- package/dist/docs/deployment.html +39 -39
- package/dist/docs/deployment.md +6 -10
- package/dist/docs/evals.html +36 -36
- package/dist/docs/evals.md +0 -3
- package/dist/docs/guides/agent-to-agent.html +36 -36
- package/dist/docs/guides/agent-to-agent.md +0 -3
- package/dist/docs/guides/bitbucket.html +36 -36
- package/dist/docs/guides/bitbucket.md +0 -3
- package/dist/docs/guides/cloud-agents.html +36 -36
- package/dist/docs/guides/cloud-agents.md +0 -4
- package/dist/docs/guides/convert-automation.html +37 -37
- package/dist/docs/guides/convert-automation.md +2 -5
- package/dist/docs/guides/github.html +37 -37
- package/dist/docs/guides/github.md +1 -4
- package/dist/docs/guides/gitlab.html +36 -36
- package/dist/docs/guides/gitlab.md +0 -3
- package/dist/docs/guides/grokbot-agents.html +37 -37
- package/dist/docs/guides/grokbot-agents.md +1 -4
- package/dist/docs/guides/hooks.html +36 -36
- package/dist/docs/guides/hooks.md +0 -3
- package/dist/docs/guides/improve.html +36 -36
- package/dist/docs/guides/improve.md +0 -3
- package/dist/docs/guides/jev.html +37 -37
- package/dist/docs/guides/jev.md +7 -3
- package/dist/docs/guides/mcp-oauth.html +37 -37
- package/dist/docs/guides/mcp-oauth.md +1 -4
- package/dist/docs/guides/opentelemetry.html +42 -40
- package/dist/docs/guides/opentelemetry.md +4 -5
- package/dist/docs/guides/slack.html +43 -43
- package/dist/docs/guides/slack.md +14 -24
- package/dist/docs/guides/webhooks.html +36 -36
- package/dist/docs/guides/webhooks.md +0 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +36 -36
- package/dist/docs/hillclimbing.md +0 -3
- package/dist/docs/index.html +38 -38
- package/dist/docs/index.md +9 -16
- package/dist/docs/llms-full.txt +156 -220
- package/dist/docs/llms.txt +1 -0
- package/dist/docs/quickstart.html +36 -36
- package/dist/docs/quickstart.md +2 -7
- package/dist/docs/reference/agent-config.html +38 -38
- package/dist/docs/reference/agent-config.md +4 -7
- package/dist/docs/reference/artifacts.html +36 -36
- package/dist/docs/reference/artifacts.md +0 -3
- package/dist/docs/reference/channels.html +37 -37
- package/dist/docs/reference/channels.md +1 -6
- package/dist/docs/reference/cli.html +38 -38
- package/dist/docs/reference/cli.md +7 -14
- package/dist/docs/reference/connections.html +38 -38
- package/dist/docs/reference/connections.md +7 -10
- package/dist/docs/reference/evals.html +36 -36
- package/dist/docs/reference/evals.md +0 -3
- package/dist/docs/reference/extensions.html +40 -39
- package/dist/docs/reference/extensions.md +15 -5
- package/dist/docs/reference/hooks.html +36 -36
- package/dist/docs/reference/hooks.md +0 -3
- package/dist/docs/reference/http-api.html +37 -37
- package/dist/docs/reference/http-api.md +1 -1
- package/dist/docs/reference/instructions.html +36 -36
- package/dist/docs/reference/instructions.md +0 -3
- package/dist/docs/reference/playground.html +36 -36
- package/dist/docs/reference/playground.md +0 -3
- package/dist/docs/reference/project-layout.html +37 -37
- package/dist/docs/reference/project-layout.md +2 -2
- package/dist/docs/reference/prompt.html +37 -37
- package/dist/docs/reference/prompt.md +0 -3
- package/dist/docs/reference/schedules.html +36 -36
- package/dist/docs/reference/schedules.md +0 -3
- package/dist/docs/reference/sessions.html +36 -36
- package/dist/docs/reference/sessions.md +0 -3
- package/dist/docs/reference/skills.html +38 -38
- package/dist/docs/reference/skills.md +0 -3
- package/dist/docs/reference/subagents.html +36 -36
- package/dist/docs/reference/subagents.md +0 -3
- package/dist/docs/reference/tools.html +36 -36
- package/dist/docs/reference/tools.md +1 -5
- package/dist/docs/templates/agentic-owners.html +36 -36
- package/dist/docs/templates/agentic-owners.md +0 -3
- package/dist/docs/templates/grokbot-agents.html +64 -0
- package/dist/docs/templates/grokbot-agents.md +55 -0
- package/dist/docs/templates/security-reviewer.html +37 -37
- package/dist/docs/templates/security-reviewer.md +0 -3
- package/dist/docs/templates/thermo-quality-review.html +36 -36
- package/dist/docs/templates/thermo-quality-review.md +3 -6
- package/dist/docs/templates/thermo-review.html +36 -36
- package/dist/docs/templates/thermo-review.md +3 -5
- package/dist/docs/templates/triage.html +37 -37
- package/dist/docs/templates/triage.md +2 -3
- package/dist/docs/troubleshooting.html +36 -36
- package/dist/docs/troubleshooting.md +3 -7
- package/dist/extensions/jev/extension.d.ts +3 -0
- package/dist/extensions/jev/extension.d.ts.map +1 -1
- package/dist/extensions/jev/extension.js +10 -1
- package/dist/extensions/jev/lib/evaluate.d.ts +4 -0
- package/dist/extensions/jev/lib/evaluate.d.ts.map +1 -1
- package/dist/extensions/jev/lib/evaluate.js +6 -2
- package/dist/extensions.d.ts +4 -2
- package/dist/extensions.d.ts.map +1 -1
- package/dist/extensions.js +5 -2
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +19 -5
- package/dist/internal/discovery/agent-config.d.ts +6 -5
- package/dist/internal/discovery/agent-config.d.ts.map +1 -1
- package/dist/internal/discovery/agent-config.js +29 -28
- package/dist/internal/discovery/agent.js +1 -1
- package/dist/internal/discovery/extensions.d.ts.map +1 -1
- package/dist/internal/discovery/extensions.js +12 -0
- package/dist/internal/hosting.d.ts +8 -0
- package/dist/internal/hosting.d.ts.map +1 -1
- package/dist/internal/hosting.js +24 -0
- package/dist/types.d.ts +18 -2
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +9 -16
- package/docs/building-with-agents.md +0 -3
- package/docs/deployment.md +6 -10
- package/docs/evals.md +0 -3
- package/docs/guides/agent-to-agent.md +0 -3
- package/docs/guides/bitbucket.md +0 -3
- package/docs/guides/cloud-agents.md +0 -4
- package/docs/guides/convert-automation.md +2 -5
- package/docs/guides/github.md +1 -4
- package/docs/guides/gitlab.md +0 -3
- package/docs/guides/grokbot-agents.md +1 -4
- package/docs/guides/hooks.md +0 -3
- package/docs/guides/improve.md +0 -3
- package/docs/guides/jev.md +7 -3
- package/docs/guides/mcp-oauth.md +1 -4
- package/docs/guides/opentelemetry.md +4 -5
- package/docs/guides/slack.md +14 -24
- package/docs/guides/webhooks.md +0 -3
- package/docs/hillclimbing.md +0 -3
- package/docs/quickstart.md +2 -7
- package/docs/reference/agent-config.md +4 -7
- package/docs/reference/artifacts.md +0 -3
- package/docs/reference/channels.md +1 -6
- package/docs/reference/cli.md +7 -14
- package/docs/reference/connections.md +7 -10
- package/docs/reference/evals.md +0 -3
- package/docs/reference/extensions.md +15 -5
- package/docs/reference/hooks.md +0 -3
- package/docs/reference/http-api.md +1 -1
- package/docs/reference/instructions.md +0 -3
- package/docs/reference/playground.md +0 -3
- package/docs/reference/project-layout.md +2 -2
- package/docs/reference/prompt.md +0 -3
- package/docs/reference/schedules.md +0 -3
- package/docs/reference/sessions.md +0 -3
- package/docs/reference/skills.md +0 -3
- package/docs/reference/subagents.md +0 -3
- package/docs/reference/tools.md +1 -5
- package/docs/templates/agentic-owners.md +0 -3
- package/docs/templates/grokbot-agents.md +60 -0
- package/docs/templates/security-reviewer.md +0 -3
- package/docs/templates/thermo-quality-review.md +3 -6
- package/docs/templates/thermo-review.md +3 -5
- package/docs/templates/triage.md +2 -3
- package/docs/troubleshooting.md +3 -7
- package/package.json +2 -10
- package/templates/agentic-owners/README.md +0 -3
- package/templates/agents-md/README.md +4 -3
- package/templates/code-wiki/README.md +0 -3
- package/templates/demo/README.md +0 -3
- package/templates/grokbot-agents/README.md +4 -6
- package/templates/security-help/README.md +12 -6
- package/templates/security-reviewer/README.md +0 -3
- package/templates/thermo-quality-review/README.md +0 -3
- package/templates/thermo-review/README.md +0 -3
- package/templates/triage/README.md +0 -3
- package/templates/triage/overlays/jira/README.md +0 -3
- package/templates/triage/overlays/linear/README.md +0 -3
- package/dist/docs/assets/chunks/@localSearchIndexroot.D5L-b34G.js +0 -1
- package/dist/docs/assets/chunks/channel.BbOGaJQP.js +0 -1
- package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.Db58HCb1.js +0 -1
- package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.Db58HCb1.js +0 -1
- package/dist/docs/assets/chunks/clone.Co4MBflG.js +0 -1
- package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.CMsKhrW0.js +0 -1
- package/dist/docs/assets/index.md.CVc_oOA3.js +0 -27
- package/dist/docs/assets/reference_playground.md.BdQzRJc2.js +0 -1
- package/dist/docs/assets/reference_sessions.md.DeRUItHD.js +0 -1
- package/dist/docs/assets/troubleshooting.md.Cw5PKa3t.js +0 -1
- package/src/artifacts.ts +0 -68
- package/src/bin/agent-serve.ts +0 -1854
- package/src/channels/bitbucket/api.ts +0 -891
- package/src/channels/bitbucket/binding.ts +0 -417
- package/src/channels/bitbucket/bitbucket-channel.ts +0 -162
- package/src/channels/bitbucket/bitbucket-webhook.ts +0 -519
- package/src/channels/bitbucket/checks.ts +0 -146
- package/src/channels/bitbucket/cursor-account.ts +0 -106
- package/src/channels/bitbucket/defaults.ts +0 -100
- package/src/channels/bitbucket/events.ts +0 -74
- package/src/channels/bitbucket/index.ts +0 -51
- package/src/channels/bitbucket/replay.ts +0 -421
- package/src/channels/bitbucket/state.ts +0 -59
- package/src/channels/bitbucket/types.ts +0 -261
- package/src/channels/change-monitors/change-monitors-channel.ts +0 -63
- package/src/channels/change-monitors/index.ts +0 -8
- package/src/channels/change-monitors/types.ts +0 -42
- package/src/channels/checks.ts +0 -185
- package/src/channels/deployments/deployments-channel.ts +0 -224
- package/src/channels/deployments/index.ts +0 -25
- package/src/channels/deployments/types.ts +0 -92
- package/src/channels/github/api.ts +0 -526
- package/src/channels/github/auth.ts +0 -293
- package/src/channels/github/binding.ts +0 -233
- package/src/channels/github/checks.ts +0 -145
- package/src/channels/github/cursor-account.ts +0 -303
- package/src/channels/github/defaults.ts +0 -168
- package/src/channels/github/events.ts +0 -67
- package/src/channels/github/forward.ts +0 -359
- package/src/channels/github/github-channel.ts +0 -2334
- package/src/channels/github/index.ts +0 -54
- package/src/channels/github/progress.ts +0 -365
- package/src/channels/github/replay.ts +0 -372
- package/src/channels/github/state.ts +0 -124
- package/src/channels/github/types.ts +0 -590
- package/src/channels/gitlab/api.ts +0 -329
- package/src/channels/gitlab/binding.ts +0 -231
- package/src/channels/gitlab/checks.ts +0 -228
- package/src/channels/gitlab/cursor-account.ts +0 -105
- package/src/channels/gitlab/defaults.ts +0 -80
- package/src/channels/gitlab/events.ts +0 -37
- package/src/channels/gitlab/gitlab-channel.ts +0 -160
- package/src/channels/gitlab/gitlab-webhook.ts +0 -509
- package/src/channels/gitlab/index.ts +0 -44
- package/src/channels/gitlab/replay.ts +0 -328
- package/src/channels/gitlab/state.ts +0 -55
- package/src/channels/gitlab/types.ts +0 -247
- package/src/channels/issues/index.ts +0 -9
- package/src/channels/issues/issues-channel.ts +0 -45
- package/src/channels/issues/types.ts +0 -38
- package/src/channels/origin/api.ts +0 -507
- package/src/channels/origin/binding.ts +0 -114
- package/src/channels/origin/checks.ts +0 -250
- package/src/channels/origin/cursor-account.ts +0 -58
- package/src/channels/origin/defaults.ts +0 -140
- package/src/channels/origin/events.ts +0 -35
- package/src/channels/origin/index.ts +0 -35
- package/src/channels/origin/origin-channel.ts +0 -815
- package/src/channels/origin/origin-webhook.ts +0 -296
- package/src/channels/origin/reactions.ts +0 -72
- package/src/channels/origin/state.ts +0 -54
- package/src/channels/origin/types.ts +0 -295
- package/src/channels/slack/agentic-delivery.ts +0 -634
- package/src/channels/slack/agentic-posted.ts +0 -135
- package/src/channels/slack/agentic-signals.ts +0 -231
- package/src/channels/slack/api.ts +0 -761
- package/src/channels/slack/approvals.ts +0 -279
- package/src/channels/slack/assistant.ts +0 -76
- package/src/channels/slack/attachments.ts +0 -596
- package/src/channels/slack/auth.ts +0 -127
- package/src/channels/slack/blocks.ts +0 -131
- package/src/channels/slack/bot-mentions.ts +0 -318
- package/src/channels/slack/channel-watch.ts +0 -314
- package/src/channels/slack/cli-project.ts +0 -249
- package/src/channels/slack/code-fences.ts +0 -171
- package/src/channels/slack/constants.ts +0 -38
- package/src/channels/slack/credentials.ts +0 -88
- package/src/channels/slack/defaults.ts +0 -714
- package/src/channels/slack/dispatch.ts +0 -1060
- package/src/channels/slack/doctor.ts +0 -338
- package/src/channels/slack/eval-directive.ts +0 -306
- package/src/channels/slack/event-id-dedupe.ts +0 -30
- package/src/channels/slack/external-policy.ts +0 -75
- package/src/channels/slack/footer.ts +0 -106
- package/src/channels/slack/inbound.ts +0 -416
- package/src/channels/slack/index.ts +0 -53
- package/src/channels/slack/init.ts +0 -169
- package/src/channels/slack/interactive.ts +0 -406
- package/src/channels/slack/links.ts +0 -23
- package/src/channels/slack/live-delivery.ts +0 -489
- package/src/channels/slack/log.ts +0 -70
- package/src/channels/slack/manifest.ts +0 -240
- package/src/channels/slack/message-body.ts +0 -82
- package/src/channels/slack/nudge-carrier.ts +0 -224
- package/src/channels/slack/nudge-store.ts +0 -334
- package/src/channels/slack/paint-start.ts +0 -93
- package/src/channels/slack/placeholder.ts +0 -358
- package/src/channels/slack/pr-ref.ts +0 -74
- package/src/channels/slack/progress-delivery.ts +0 -138
- package/src/channels/slack/reasoning-card.ts +0 -125
- package/src/channels/slack/reasoning-chip.ts +0 -41
- package/src/channels/slack/recording-sink.ts +0 -167
- package/src/channels/slack/redact.ts +0 -6
- package/src/channels/slack/reply-options.ts +0 -279
- package/src/channels/slack/setup.ts +0 -125
- package/src/channels/slack/slack-channel.ts +0 -744
- package/src/channels/slack/socket-mode.ts +0 -140
- package/src/channels/slack/stop.ts +0 -25
- package/src/channels/slack/stream.ts +0 -66
- package/src/channels/slack/thread-context.ts +0 -485
- package/src/channels/slack/tool-approval-events.ts +0 -233
- package/src/channels/slack/tool-cards.ts +0 -198
- package/src/channels/slack/tool-chip.ts +0 -34
- package/src/channels/slack/types.ts +0 -787
- package/src/channels/slack/upload.ts +0 -137
- package/src/channels.ts +0 -543
- package/src/connections.ts +0 -153
- package/src/dashboard-session-url.ts +0 -30
- package/src/evals/assertions.ts +0 -688
- package/src/evals/expect.ts +0 -250
- package/src/evals/judge.ts +0 -329
- package/src/evals/loaders.ts +0 -111
- package/src/evals/matchers.ts +0 -223
- package/src/evals/reporters.ts +0 -257
- package/src/evals/results.ts +0 -130
- package/src/evals/run-facts.ts +0 -306
- package/src/evals.ts +0 -709
- package/src/extensions/cursor-cloud-agents/extension.ts +0 -131
- package/src/extensions/cursor-cloud-agents/lib/backend.ts +0 -386
- package/src/extensions/cursor-cloud-agents/lib/channels.ts +0 -11
- package/src/extensions/cursor-cloud-agents/lib/fleet.ts +0 -98
- package/src/extensions/cursor-cloud-agents/lib/inputs.ts +0 -5
- package/src/extensions/cursor-cloud-agents/lib/repos.ts +0 -80
- package/src/extensions/cursor-cloud-agents/lib/transcript.ts +0 -19
- package/src/extensions/cursor-cloud-agents/skills/handoff.md +0 -37
- package/src/extensions/cursor-cloud-agents/tools/cancel.ts +0 -21
- package/src/extensions/cursor-cloud-agents/tools/dump.ts +0 -47
- package/src/extensions/cursor-cloud-agents/tools/get.ts +0 -18
- package/src/extensions/cursor-cloud-agents/tools/launch.ts +0 -62
- package/src/extensions/cursor-cloud-agents/tools/list.ts +0 -31
- package/src/extensions/cursor-cloud-agents/tools/reply.ts +0 -36
- package/src/extensions/cursor-grokbot-agents/extension.ts +0 -132
- package/src/extensions/cursor-grokbot-agents/lib/agents.ts +0 -42
- package/src/extensions/cursor-grokbot-agents/lib/backend.ts +0 -208
- package/src/extensions/cursor-grokbot-agents/lib/contacts.ts +0 -74
- package/src/extensions/cursor-grokbot-agents/lib/conversation.ts +0 -260
- package/src/extensions/cursor-grokbot-agents/lib/inputs.ts +0 -21
- package/src/extensions/cursor-grokbot-agents/lib/lock.ts +0 -26
- package/src/extensions/cursor-grokbot-agents/skills/consult.md +0 -40
- package/src/extensions/cursor-grokbot-agents/tools/ask.ts +0 -32
- package/src/extensions/cursor-grokbot-agents/tools/check.ts +0 -25
- package/src/extensions/cursor-grokbot-agents/tools/interrupt.ts +0 -17
- package/src/extensions/cursor-grokbot-agents/tools/list.ts +0 -26
- package/src/extensions/improve/extension.ts +0 -69
- package/src/extensions/improve/skills/yourself.ts +0 -50
- package/src/extensions/jev/extension.ts +0 -95
- package/src/extensions/jev/lib/evaluate.ts +0 -266
- package/src/extensions/jev/skills/gated-write.md +0 -25
- package/src/extensions/jev/skills/questions.md +0 -33
- package/src/extensions/jev/tools/evaluate.ts +0 -82
- package/src/extensions/slack-communication/evals/harness.ts +0 -705
- package/src/extensions/slack-communication/evals/slack-communication.eval.ts +0 -830
- package/src/extensions/slack-communication/evals/slack-placeholder.eval.ts +0 -172
- package/src/extensions/slack-communication/extension.ts +0 -44
- package/src/extensions/slack-communication/skills/slack-voice.ts +0 -56
- package/src/extensions/slack-communication/tools/delete-message.ts +0 -33
- package/src/extensions/slack-communication/tools/edit-message.ts +0 -42
- package/src/extensions/slack-communication/tools/post-card.ts +0 -28
- package/src/extensions/slack-communication/tools/post-file.ts +0 -82
- package/src/extensions/slack-communication/tools/post-update.ts +0 -40
- package/src/extensions/slack-communication/tools/react.ts +0 -30
- package/src/extensions/slack-communication/tools/set-status.ts +0 -21
- package/src/extensions.ts +0 -179
- package/src/files-backends/agent-store-presigned-url.ts +0 -11
- package/src/files-backends/cursor-hosted.ts +0 -1212
- package/src/files-backends/local-fs.ts +0 -163
- package/src/files.ts +0 -293
- package/src/filesystem.ts +0 -2005
- package/src/hooks.ts +0 -30
- package/src/index.ts +0 -259
- package/src/internal/advertise-tools.ts +0 -370
- package/src/internal/approval-gate.ts +0 -103
- package/src/internal/artifacts-store.ts +0 -836
- package/src/internal/as-of.ts +0 -257
- package/src/internal/auth.ts +0 -46
- package/src/internal/authored-alias-hooks.ts +0 -138
- package/src/internal/authored-loaders.ts +0 -188
- package/src/internal/bounded-int.ts +0 -24
- package/src/internal/bounded.ts +0 -25
- package/src/internal/brand.ts +0 -28
- package/src/internal/browser-origin-guard.ts +0 -67
- package/src/internal/builtin-tools/artifacts.ts +0 -62
- package/src/internal/builtin-tools/index.ts +0 -53
- package/src/internal/builtin-tools/reminders.ts +0 -145
- package/src/internal/channel-dispatch.ts +0 -129
- package/src/internal/channel-route-dispatch.ts +0 -58
- package/src/internal/channel-state.ts +0 -83
- package/src/internal/chat-client.ts +0 -594
- package/src/internal/cli-ax.ts +0 -2318
- package/src/internal/cli-bitbucket.ts +0 -438
- package/src/internal/cli-convert-automation.ts +0 -335
- package/src/internal/cli-cursor.ts +0 -282
- package/src/internal/cli-deploy.ts +0 -2519
- package/src/internal/cli-docs.ts +0 -192
- package/src/internal/cli-github.ts +0 -806
- package/src/internal/cli-gitlab.ts +0 -452
- package/src/internal/cli-images.ts +0 -672
- package/src/internal/cli-manifest.ts +0 -56
- package/src/internal/cli-mcp-oauth.ts +0 -396
- package/src/internal/cli-mcp.ts +0 -808
- package/src/internal/cli-skills.ts +0 -98
- package/src/internal/cli-slack-manual.ts +0 -418
- package/src/internal/cli-slack.ts +0 -874
- package/src/internal/cloud-merge.ts +0 -51
- package/src/internal/cloud-turn-cost.ts +0 -159
- package/src/internal/connection-env.ts +0 -106
- package/src/internal/continuation-channel.ts +0 -233
- package/src/internal/continuation-identity.ts +0 -935
- package/src/internal/conversation-mirror.ts +0 -423
- package/src/internal/convert-automation/convert-workflow.ts +0 -1377
- package/src/internal/convert-automation/slug.ts +0 -29
- package/src/internal/convert-automation/types.ts +0 -212
- package/src/internal/cron.ts +0 -76
- package/src/internal/cursor/account-mcp.ts +0 -692
- package/src/internal/cursor/backend-client.ts +0 -984
- package/src/internal/cursor/bound-secret-file.ts +0 -101
- package/src/internal/cursor/credentials.ts +0 -560
- package/src/internal/cursor/cursor-api-transport.ts +0 -65
- package/src/internal/cursor/github-credentials.ts +0 -228
- package/src/internal/cursor/hosted-bound-secrets.ts +0 -71
- package/src/internal/cursor/hosted-deployment-secrets.ts +0 -100
- package/src/internal/cursor/hosted-store-secrets.ts +0 -152
- package/src/internal/cursor/session-token.ts +0 -111
- package/src/internal/cursor/store-api-client.ts +0 -323
- package/src/internal/cursor-account-mcp-auth.ts +0 -303
- package/src/internal/cursor-agent-template.ts +0 -50
- package/src/internal/cursor-deployment-relay.ts +0 -315
- package/src/internal/cursor-event-relay.ts +0 -675
- package/src/internal/cursor-relay-core.ts +0 -563
- package/src/internal/deferred-channel-session.ts +0 -61
- package/src/internal/deploy-client.ts +0 -1574
- package/src/internal/deploy-manifest.ts +0 -361
- package/src/internal/deploy-source.ts +0 -144
- package/src/internal/describe-error.ts +0 -32
- package/src/internal/dev-mode.ts +0 -20
- package/src/internal/discovery/agent-config.ts +0 -288
- package/src/internal/discovery/agent.ts +0 -381
- package/src/internal/discovery/channels.ts +0 -100
- package/src/internal/discovery/connections.ts +0 -516
- package/src/internal/discovery/context.ts +0 -38
- package/src/internal/discovery/cursor-plugin.ts +0 -628
- package/src/internal/discovery/extension-overlay.ts +0 -247
- package/src/internal/discovery/extensions.ts +0 -490
- package/src/internal/discovery/fs.ts +0 -103
- package/src/internal/discovery/hooks.ts +0 -63
- package/src/internal/discovery/info.ts +0 -174
- package/src/internal/discovery/instructions.ts +0 -98
- package/src/internal/discovery/modules.ts +0 -107
- package/src/internal/discovery/names.ts +0 -45
- package/src/internal/discovery/project.ts +0 -319
- package/src/internal/discovery/reminders.ts +0 -65
- package/src/internal/discovery/runtime.ts +0 -158
- package/src/internal/discovery/schedules.ts +0 -120
- package/src/internal/discovery/seed-files.ts +0 -34
- package/src/internal/discovery/services.ts +0 -151
- package/src/internal/discovery/skills.ts +0 -174
- package/src/internal/discovery/subagent.ts +0 -138
- package/src/internal/discovery/tools.ts +0 -170
- package/src/internal/discovery.ts +0 -27
- package/src/internal/distribution.ts +0 -126
- package/src/internal/docs-site.ts +0 -248
- package/src/internal/duration.ts +0 -37
- package/src/internal/env-file.ts +0 -83
- package/src/internal/eval-judge-model.ts +0 -142
- package/src/internal/eval-run-store.ts +0 -585
- package/src/internal/eval-runner.ts +0 -823
- package/src/internal/evals-client.ts +0 -475
- package/src/internal/event-log.ts +0 -342
- package/src/internal/event-mapper.ts +0 -811
- package/src/internal/expected-delivery-failure.ts +0 -34
- package/src/internal/factory-resource-channel.ts +0 -103
- package/src/internal/filesystem/errors.ts +0 -207
- package/src/internal/filesystem/format.ts +0 -253
- package/src/internal/filesystem/merge.ts +0 -192
- package/src/internal/filesystem/nearest.ts +0 -112
- package/src/internal/filesystem/scratch.ts +0 -239
- package/src/internal/filesystem/sdk-schemas.ts +0 -204
- package/src/internal/filesystem/tools.ts +0 -804
- package/src/internal/filesystem/walk.ts +0 -366
- package/src/internal/fixtures/units-server.ts +0 -52
- package/src/internal/framework-file-storage.ts +0 -357
- package/src/internal/framework-storage-selection.ts +0 -156
- package/src/internal/frontmatter.ts +0 -108
- package/src/internal/github-fanout.ts +0 -144
- package/src/internal/guest-network.ts +0 -391
- package/src/internal/handleAgentServeTrigger.ts +0 -286
- package/src/internal/host-files.ts +0 -341
- package/src/internal/host-kv.ts +0 -81
- package/src/internal/host-otel.ts +0 -231
- package/src/internal/host-platforms.ts +0 -170
- package/src/internal/hosted-admission-adapter.ts +0 -47
- package/src/internal/hosted-admission-context.ts +0 -37
- package/src/internal/hosted-catch-protocol.ts +0 -112
- package/src/internal/hosted-catch.ts +0 -179
- package/src/internal/hosted-delivery-protocol.ts +0 -824
- package/src/internal/hosted-delivery.ts +0 -1410
- package/src/internal/hosted-execution-diag.ts +0 -291
- package/src/internal/hosted-execution-flush.ts +0 -136
- package/src/internal/hosted-execution-latency.ts +0 -301
- package/src/internal/hosted-managed-automation.ts +0 -116
- package/src/internal/hosting.ts +0 -114
- package/src/internal/http-channel-define.ts +0 -25
- package/src/internal/http-channel.ts +0 -669
- package/src/internal/http-control-plane-session.ts +0 -110
- package/src/internal/ids.ts +0 -39
- package/src/internal/incoming-headers-to-fetch.ts +0 -45
- package/src/internal/init-interview.ts +0 -360
- package/src/internal/init-project.ts +0 -711
- package/src/internal/init-scaffold.ts +0 -182
- package/src/internal/inline-stdio-define-stub.ts +0 -4
- package/src/internal/inline-stdio-mcp.ts +0 -230
- package/src/internal/install-cursor-skills.ts +0 -604
- package/src/internal/is-record.ts +0 -3
- package/src/internal/json-schema.ts +0 -95
- package/src/internal/legacy-command-guard.ts +0 -66
- package/src/internal/listen-port.ts +0 -85
- package/src/internal/local-control-plane.ts +0 -442
- package/src/internal/local-env.ts +0 -59
- package/src/internal/local-ingress.ts +0 -90
- package/src/internal/log-ring.ts +0 -59
- package/src/internal/logs-client.ts +0 -477
- package/src/internal/map-pool.ts +0 -39
- package/src/internal/mcp-endpoint.ts +0 -747
- package/src/internal/mcp-host.ts +0 -509
- package/src/internal/mcp-oauth-on-demand.ts +0 -337
- package/src/internal/mcp-oauth-persist.ts +0 -118
- package/src/internal/mcp-oauth.ts +0 -549
- package/src/internal/net.ts +0 -29
- package/src/internal/open-browser.ts +0 -41
- package/src/internal/otel/attrs.ts +0 -16
- package/src/internal/otel/collector.ts +0 -937
- package/src/internal/otel/config.ts +0 -287
- package/src/internal/otel/http.ts +0 -122
- package/src/internal/otel/mcp.ts +0 -56
- package/src/internal/otel/propagation.ts +0 -17
- package/src/internal/otel/runtime.ts +0 -441
- package/src/internal/package-inline-stdio.ts +0 -114
- package/src/internal/peer-connections.ts +0 -96
- package/src/internal/platform-schedule-sync.ts +0 -75
- package/src/internal/platform-timers.ts +0 -365
- package/src/internal/playground/static.ts +0 -278
- package/src/internal/playground/toolchain.ts +0 -270
- package/src/internal/playground-proxy.ts +0 -564
- package/src/internal/process-memory-telemetry.ts +0 -334
- package/src/internal/project-layout.ts +0 -31
- package/src/internal/prompt-context.ts +0 -116
- package/src/internal/reminder-control-plane-protocol.ts +0 -482
- package/src/internal/reminder-runner.ts +0 -1559
- package/src/internal/reminder-schedule.ts +0 -104
- package/src/internal/reminder-store.ts +0 -176
- package/src/internal/request-error-status.ts +0 -95
- package/src/internal/request-headers.ts +0 -21
- package/src/internal/resolve-prod-target.ts +0 -334
- package/src/internal/resolved-connections.ts +0 -190
- package/src/internal/router.ts +0 -143
- package/src/internal/run-client.ts +0 -354
- package/src/internal/runtime-dispatch-runner.ts +0 -50
- package/src/internal/sanitize-mcp-tool-name.ts +0 -31
- package/src/internal/schedule-runner.ts +0 -154
- package/src/internal/scm/pr-url.ts +0 -245
- package/src/internal/sdk-runner.ts +0 -959
- package/src/internal/serve-restart.ts +0 -113
- package/src/internal/server.ts +0 -3147
- package/src/internal/session-cost.ts +0 -208
- package/src/internal/session-engine.ts +0 -5622
- package/src/internal/session-links.ts +0 -84
- package/src/internal/session-run-log.ts +0 -535
- package/src/internal/session-store.ts +0 -307
- package/src/internal/session-urls.ts +0 -32
- package/src/internal/sessions-client.ts +0 -239
- package/src/internal/skill-catalog.ts +0 -66
- package/src/internal/skills-sync.ts +0 -55
- package/src/internal/slack-provision-client.ts +0 -544
- package/src/internal/slack-thread-key.ts +0 -11
- package/src/internal/sleep.ts +0 -3
- package/src/internal/start-log-store.ts +0 -164
- package/src/internal/steering.ts +0 -17
- package/src/internal/storage-coordinator.ts +0 -800
- package/src/internal/storage-roles.ts +0 -86
- package/src/internal/storage.ts +0 -1
- package/src/internal/store-api-protocol.ts +0 -205
- package/src/internal/store-mirror.ts +0 -73
- package/src/internal/stream-progress.ts +0 -294
- package/src/internal/terminal-style.ts +0 -133
- package/src/internal/testing/factory-resource-fixtures.ts +0 -84
- package/src/internal/testing/scm-continuation-fixtures.ts +0 -285
- package/src/internal/tool-catalog.ts +0 -104
- package/src/internal/tool-policy.ts +0 -222
- package/src/internal/tool-result.ts +0 -50
- package/src/internal/trajectory.ts +0 -418
- package/src/internal/turn-cost.ts +0 -136
- package/src/internal/turn-governor.ts +0 -145
- package/src/internal/turn-lease-store.ts +0 -144
- package/src/internal/turn-outbox.ts +0 -77
- package/src/internal/update-check.ts +0 -517
- package/src/internal/v2-hosting.ts +0 -52
- package/src/internal/workspace-sync.ts +0 -114
- package/src/internal/workspace.ts +0 -505
- package/src/kv/memory-kv.ts +0 -94
- package/src/kv.ts +0 -18
- package/src/memory.ts +0 -487
- package/src/otel-public.ts +0 -24
- package/src/otel.ts +0 -320
- package/src/prompt.ts +0 -80
- package/src/reminders.ts +0 -65
- package/src/schedules.ts +0 -31
- package/src/skills.ts +0 -27
- package/src/storage-backends/cursor-hosted-v2.ts +0 -204
- package/src/storage-backends/cursor-hosted.ts +0 -388
- package/src/storage-backends/file-kv.ts +0 -276
- package/src/storage-protocol.ts +0 -261
- package/src/storage.ts +0 -408
- package/src/tools.ts +0 -166
- package/src/types.ts +0 -3776
- /package/dist/docs/assets/{guides_jev.md.BNDm14Gy.lean.js → guides_jev.md.D7XH4JH3.lean.js} +0 -0
- /package/dist/docs/assets/{reference_http-api.md.BZOvhCqY.lean.js → reference_http-api.md.DbFNOubV.lean.js} +0 -0
- /package/dist/docs/assets/{reference_project-layout.md.CpLQ9j4K.lean.js → reference_project-layout.md.C2JMRh7p.lean.js} +0 -0
|
@@ -1,1212 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cursor-managed durable files for hosted agents — the backend that puts
|
|
3
|
-
* `ctx.host.files` bytes in an Agent Store on S3.
|
|
4
|
-
*
|
|
5
|
-
* One credential throughout: the engine's pod `CURSOR_API_KEY`. It authorizes
|
|
6
|
-
* every call, and the bytes then move between this process and presigned S3
|
|
7
|
-
* URLs. File contents never transit the control plane, so a large artifact
|
|
8
|
-
* costs S3 bandwidth rather than backend memory.
|
|
9
|
-
*
|
|
10
|
-
* By default everything goes through the public API under
|
|
11
|
-
* `/v0/agent-serve-files`, the same surface any API-key holder gets — a
|
|
12
|
-
* self-hosted deployment, a CI job, or a customer script can drive the same
|
|
13
|
-
* store with the same requests. Cursor-managed v2 pods can instead be pinned
|
|
14
|
-
* (per deployment, at task-time bind) onto the engine-facing factory store API
|
|
15
|
-
* under `/internal/agentsdk/store`: same host, same credential, same stores
|
|
16
|
-
* and S3 objects, but flush-shaped — one control-plane request settles a whole
|
|
17
|
-
* `putMany` batch or a conditional delete, where `/v0` costs a presign POST
|
|
18
|
-
* plus stat GETs and a delete POST. The pin fails safe: an absent or
|
|
19
|
-
* unrecognized protocol value, or a source id that maps to neither deployment
|
|
20
|
-
* store role, keeps `/v0`.
|
|
21
|
-
*
|
|
22
|
-
* Two things follow. There is no Agent Store token: a token narrows an identity
|
|
23
|
-
* to one store and one capability, which is what a caller that cannot hold a
|
|
24
|
-
* credential needs (an in-pod FUSE daemon). This process holds the credential
|
|
25
|
-
* the token would be minted from, so a token bought a round trip, an expiry to
|
|
26
|
-
* manage, and a second auth path, and no privilege reduction. And there is no
|
|
27
|
-
* direct call to the store service: the API layer owns the policy, and the
|
|
28
|
-
* engine never addresses it.
|
|
29
|
-
*
|
|
30
|
-
* There is also no store lookup. Every request names this deployment's file
|
|
31
|
-
* store by the `agentStoreSourceId` the deploy pipeline pinned into the pod
|
|
32
|
-
* env as `AGENT_SERVE_STORE_SOURCE_ID` — provisioning the store at the same
|
|
33
|
-
* time, so by boot the store exists and the id resolves. The id is an
|
|
34
|
-
* address, not a credential: the server maps it to a store and authorizes
|
|
35
|
-
* every call against the deployment's own live service account, so holding
|
|
36
|
-
* it grants nothing, and this process never learns a store id it could use
|
|
37
|
-
* to ask for anyone else's.
|
|
38
|
-
*
|
|
39
|
-
* Locally, point `CURSOR_API_BASE_URL` at a backend that serves
|
|
40
|
-
* `/v0/agent-serve-files` and use a deployment SA key; off hosting the
|
|
41
|
-
* local-disk sink is the default instead.
|
|
42
|
-
*
|
|
43
|
-
* Writes are conditional. The store rejects unconditional writes, so every
|
|
44
|
-
* `put` first learns the current etag: `new` for a path we believe is absent,
|
|
45
|
-
* `etag` to replace what we last observed. A raced write comes back 412 and
|
|
46
|
-
* surfaces as {@link FileConflictError}, which is why this backend reports
|
|
47
|
-
* `etags: true` and `host.files.capabilities.conflictDetection` is honestly
|
|
48
|
-
* true.
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
import { createHash } from "node:crypto";
|
|
52
|
-
import {
|
|
53
|
-
FileConflictError,
|
|
54
|
-
type FileSink,
|
|
55
|
-
type FileSinkBody,
|
|
56
|
-
type FileSinkEntry,
|
|
57
|
-
type FileSinkPutResult,
|
|
58
|
-
} from "../files.js";
|
|
59
|
-
import { mapBounded } from "../internal/bounded.js";
|
|
60
|
-
import {
|
|
61
|
-
type CursorApiTransportOptions,
|
|
62
|
-
resolveCursorApiBaseUrl,
|
|
63
|
-
resolveCursorApiFetch,
|
|
64
|
-
resolveCursorApiKey,
|
|
65
|
-
} from "../internal/cursor/cursor-api-transport.js";
|
|
66
|
-
import {
|
|
67
|
-
resolveDeploymentStoreSourceIdSync,
|
|
68
|
-
resolveFrameworkStoreSourceIdSync,
|
|
69
|
-
resolveStoreProtocolSync,
|
|
70
|
-
storeProtocolEnablesFactoryFlush,
|
|
71
|
-
} from "../internal/cursor/hosted-store-secrets.js";
|
|
72
|
-
import { createAgentStorePresignedUrlValidatorForBackend } from "./agent-store-presigned-url.js";
|
|
73
|
-
|
|
74
|
-
const AGENT_SERVE_FILES_PATH = "/v0/agent-serve-files";
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Engine-facing store API on the factory service — same host, different path
|
|
78
|
-
* family. Selected per pod by the task-bound protocol pin
|
|
79
|
-
* ({@link resolveStoreProtocolSync}); the server resolves the deployment and
|
|
80
|
-
* its stores from the pod credential, so requests carry a store *role*
|
|
81
|
-
* (`deployment` | `framework`), never a store id. One flush request replaces
|
|
82
|
-
* the presign POST plus the stat GETs and delete POST a turn costs on `/v0`.
|
|
83
|
-
*/
|
|
84
|
-
const AGENTSDK_STORE_PATH = "/internal/agentsdk/store";
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Pinned by the deploy pipeline for every Cursor-managed deployment — which
|
|
88
|
-
* provisions the file store it names in the same step — and reserved from
|
|
89
|
-
* user secrets. Its presence is the signal that a durable store exists and
|
|
90
|
-
* the hosted control plane is reachable; a deployment without one (no
|
|
91
|
-
* service account yet) has no store, and this backend is not selected.
|
|
92
|
-
*/
|
|
93
|
-
export const AGENT_SERVE_STORE_SOURCE_ID_ENV_VAR = "AGENT_SERVE_STORE_SOURCE_ID";
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* The deployment's **framework-level** file store, distinct from the
|
|
97
|
-
* user-level store above. The user store carries author-facing bytes
|
|
98
|
-
* (`ctx.host.files`, skills, memory, artifacts) and is browsable and
|
|
99
|
-
* mountable; the framework store carries engine-owned session state —
|
|
100
|
-
* session records, event transcripts, hosted-execution run logs,
|
|
101
|
-
* continuation index, and the SDK conversation mirror — and is never
|
|
102
|
-
* exposed to authors or models.
|
|
103
|
-
* Provisioned by the v2 deploy pipeline alongside the user store; v1
|
|
104
|
-
* deployments have only the user store.
|
|
105
|
-
*/
|
|
106
|
-
export const AGENT_SERVE_FRAMEWORK_STORE_SOURCE_ID_ENV_VAR =
|
|
107
|
-
"AGENT_SERVE_FRAMEWORK_STORE_SOURCE_ID";
|
|
108
|
-
|
|
109
|
-
/** Cap on the per-process etag cache; a miss costs a lookup, not correctness. */
|
|
110
|
-
const MAX_REMEMBERED_ETAGS = 1_000;
|
|
111
|
-
|
|
112
|
-
/** Bounded directory page for point lookups. */
|
|
113
|
-
const DIRECTORY_PAGE_SIZE = 1_000;
|
|
114
|
-
/**
|
|
115
|
-
* Paths per presign call in {@link FileSink.getMany}, matching the factory
|
|
116
|
-
* `/read` route's server-side `MAX_READ_PATHS`. The whole point of the batch:
|
|
117
|
-
* the factory wire's per-deployment rate limiter counts requests, not files,
|
|
118
|
-
* so a restore that used to spend N `/read` calls now spends ceil(N/100).
|
|
119
|
-
*/
|
|
120
|
-
const READ_PRESIGN_BATCH_MAX = 100;
|
|
121
|
-
/**
|
|
122
|
-
* Concurrent presigned-URL downloads inside one {@link FileSink.getMany}
|
|
123
|
-
* batch — the S3 fan-out, not the control-plane call count. Matches the
|
|
124
|
-
* conversation mirror's historical `HYDRATE_DOWNLOAD_CONCURRENCY`.
|
|
125
|
-
*/
|
|
126
|
-
const GET_MANY_DOWNLOAD_CONCURRENCY = 8;
|
|
127
|
-
/** Stop paging a pathological directory rather than scanning forever. */
|
|
128
|
-
const MAX_DIRECTORY_PAGES = 20;
|
|
129
|
-
/**
|
|
130
|
-
* Total page budget for a full `list()` walk of a store the unscoped listing
|
|
131
|
-
* could not carry (~1M entries at the page size above). Past it, fail loudly:
|
|
132
|
-
* a silently partial listing would be mistaken for the whole scope.
|
|
133
|
-
*/
|
|
134
|
-
const MAX_LIST_WALK_PAGES = 1_000;
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* True on Cursor-managed hosting with a provisioned file store.
|
|
138
|
-
*
|
|
139
|
-
* Keyed on the store source id alone, deliberately: the credential is not part
|
|
140
|
-
* of the test. If it were, a missing or regressed `CURSOR_API_KEY` would
|
|
141
|
-
* silently select pod-local disk on a hosted pod and accept writes that vanish
|
|
142
|
-
* with the pod. Instead the store is detected, and a missing credential fails
|
|
143
|
-
* loudly when the sink is used.
|
|
144
|
-
*/
|
|
145
|
-
export function isCursorHostedFilesAvailable(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
146
|
-
return resolveDeploymentStoreSourceIdSync(env) !== undefined;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Local `serve`/`run` with a personal `CURSOR_API_KEY` can address
|
|
151
|
-
* `/v0/agent-serve-files/user`. Hosted deployments have a store source id
|
|
152
|
-
* and must not take this path — they own the deployment store instead.
|
|
153
|
-
*/
|
|
154
|
-
export function isCursorUserStoreFilesAvailable(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
155
|
-
if (isCursorHostedFilesAvailable(env)) {
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
// Vitest inherits the developer's shell. User-store sync writes durable
|
|
159
|
-
// keys; skip it in the test runner so `pnpm test` cannot poison
|
|
160
|
-
// `agent-serve/<agent>/skills/` on the caller's USER store.
|
|
161
|
-
if (env.VITEST === "true") {
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
const key = env.CURSOR_API_KEY?.trim();
|
|
165
|
-
return key !== undefined && key !== "";
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Base URL / credential / fetch resolution is the shared
|
|
170
|
-
* {@link CursorApiTransportOptions} (`internal/cursor/cursor-api-transport.ts`),
|
|
171
|
-
* so this sink and the domain-lane store API client resolve identically.
|
|
172
|
-
*/
|
|
173
|
-
export interface CursorHostedFilesOptions extends CursorApiTransportOptions {
|
|
174
|
-
/** File store to address (default: `AGENT_SERVE_STORE_SOURCE_ID`). */
|
|
175
|
-
storeSourceId?: string;
|
|
176
|
-
/**
|
|
177
|
-
* Lazy store source id, re-resolved on every call and preferred over
|
|
178
|
-
* {@link storeSourceId} — freeze bind drops ids into `/run/cursor/secrets`
|
|
179
|
-
* after serve start without restarting July.
|
|
180
|
-
*/
|
|
181
|
-
resolveStoreSourceId?: () => string | undefined;
|
|
182
|
-
/**
|
|
183
|
-
* Lazy store protocol pin (default: env, then the task-bound file). When it
|
|
184
|
-
* enables the factory flush wire (`factory-flush-v1`, or the superset
|
|
185
|
-
* domain-API pin `factory-api-v1`) and the source id maps to a deployment
|
|
186
|
-
* store role, requests ride the factory store API instead of
|
|
187
|
-
* `/v0/agent-serve-files`. Any other value keeps `/v0`.
|
|
188
|
-
*/
|
|
189
|
-
resolveStoreProtocol?: () => string | undefined;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* What a file operation needs: where this deployment's files live, and which
|
|
194
|
-
* wire reaches them. `v0` addresses the public API by store source id; `flush`
|
|
195
|
-
* addresses the factory store API by role and lets the server resolve.
|
|
196
|
-
*/
|
|
197
|
-
type FilesTarget =
|
|
198
|
-
| { wire: "v0"; path(suffix?: string): string }
|
|
199
|
-
| { wire: "flush"; store: "deployment" | "framework" };
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Wire shapes from `/v0/agent-serve-files`. Declared loosely on purpose: this is a
|
|
203
|
-
* versioned public API, and a client that hard-required every field would break
|
|
204
|
-
* on an additive change it could otherwise ignore.
|
|
205
|
-
*/
|
|
206
|
-
interface ApiFile {
|
|
207
|
-
path?: string;
|
|
208
|
-
sizeBytes?: number;
|
|
209
|
-
etag?: string;
|
|
210
|
-
updatedAt?: string;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
interface ApiFileListing {
|
|
214
|
-
files?: ApiFile[];
|
|
215
|
-
complete?: boolean;
|
|
216
|
-
/** Immediate subdirectories; only returned by the `?path=` form. */
|
|
217
|
-
directories?: string[];
|
|
218
|
-
nextPageToken?: string;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
interface ApiPresignResponse {
|
|
222
|
-
files?: Array<{
|
|
223
|
-
path?: string;
|
|
224
|
-
url?: string;
|
|
225
|
-
headers?: Record<string, string>;
|
|
226
|
-
}>;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
interface ApiDeleteResponse {
|
|
230
|
-
files?: Array<{ path?: string; status?: string; etag?: string }>;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Per-op outcome from the factory store API's `flush` route, index-aligned
|
|
235
|
-
* with the request ops. A put answers `url`/`headers`; a delete answers
|
|
236
|
-
* `status`; declared loosely for the same additive-change reason as the
|
|
237
|
-
* `/v0` shapes above.
|
|
238
|
-
*/
|
|
239
|
-
interface FlushWireOpOutcome {
|
|
240
|
-
path?: string;
|
|
241
|
-
url?: string;
|
|
242
|
-
headers?: Record<string, string>;
|
|
243
|
-
error?: string;
|
|
244
|
-
status?: string;
|
|
245
|
-
etag?: string;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
interface FlushWireResponse {
|
|
249
|
-
ops?: FlushWireOpOutcome[];
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
type HostedFilesMethods = Omit<FileSink, "get" | "getMany"> & {
|
|
253
|
-
/** Narrower than {@link FileSink.get}: this backend always reports the etag. */
|
|
254
|
-
get(key: string): Promise<FileSinkBody | undefined>;
|
|
255
|
-
/** Always present here (and narrowed like {@link get}); optional on {@link FileSink}. */
|
|
256
|
-
getMany(keys: readonly string[]): Promise<Array<FileSinkBody | undefined>>;
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Cursor-hosted {@link FileSink}, selected on Cursor-managed hosting.
|
|
261
|
-
*
|
|
262
|
-
* Stateless apart from the etag cache below: every request names this
|
|
263
|
-
* deployment's store by its pinned source id, so there is nothing to
|
|
264
|
-
* resolve, hold, or invalidate.
|
|
265
|
-
*/
|
|
266
|
-
export function cursorHostedFiles(options: CursorHostedFilesOptions = {}): HostedFilesMethods {
|
|
267
|
-
/**
|
|
268
|
-
* Etags observed per key, so a replace can carry a precondition without
|
|
269
|
-
* re-reading. Bounded: a resident engine is long-lived and touches an
|
|
270
|
-
* unbounded number of keys over its lifetime. Oldest-out is fine — a miss
|
|
271
|
-
* costs one lookup, not correctness.
|
|
272
|
-
*/
|
|
273
|
-
const observedEtags = new Map<string, string>();
|
|
274
|
-
const rememberEtag = (key: string, etag: string): void => {
|
|
275
|
-
if (observedEtags.size >= MAX_REMEMBERED_ETAGS) {
|
|
276
|
-
const oldest = observedEtags.keys().next();
|
|
277
|
-
if (!oldest.done) {
|
|
278
|
-
observedEtags.delete(oldest.value);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
observedEtags.set(key, etag);
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Run `call` against this deployment's files.
|
|
286
|
-
*
|
|
287
|
-
* No network call and nothing cached: the store source id and protocol pin
|
|
288
|
-
* come from the environment (or task-bound files) and the server maps them
|
|
289
|
-
* on every request. Resolved per call rather than once at construction so a
|
|
290
|
-
* misconfigured environment fails where the operation is, not where the
|
|
291
|
-
* sink was created — and so a protocol pin bound after serve start takes
|
|
292
|
-
* effect without a restart.
|
|
293
|
-
*/
|
|
294
|
-
async function withFiles<T>(call: (files: FilesTarget) => Promise<T>): Promise<T> {
|
|
295
|
-
const sourceId = resolveStoreSourceId(options);
|
|
296
|
-
const protocol = options.resolveStoreProtocol?.() ?? resolveStoreProtocolSync();
|
|
297
|
-
if (storeProtocolEnablesFactoryFlush(protocol)) {
|
|
298
|
-
// The factory API addresses stores by role, so map the pinned source id
|
|
299
|
-
// back to the role it was pinned from. A source id matching neither (a
|
|
300
|
-
// local-dev override) has no role there and stays on `/v0`, as does any
|
|
301
|
-
// protocol value this runtime does not recognize.
|
|
302
|
-
if (sourceId === resolveFrameworkStoreSourceIdSync()) {
|
|
303
|
-
return await call({ wire: "flush", store: "framework" });
|
|
304
|
-
}
|
|
305
|
-
if (sourceId === resolveDeploymentStoreSourceIdSync()) {
|
|
306
|
-
return await call({ wire: "flush", store: "deployment" });
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
return await call({
|
|
310
|
-
wire: "v0",
|
|
311
|
-
path: (suffix = "") => `/${encodeURIComponent(sourceId)}${suffix}`,
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Fetch one presigned read URL, shared by `get` and `getMany`. A 404 on
|
|
317
|
-
* the object GET is absence (a delete raced the presign).
|
|
318
|
-
*
|
|
319
|
-
* A 403 here is deliberately *not* absence. Absence was already settled
|
|
320
|
-
* by the presign: the store refuses to presign a path with no live
|
|
321
|
-
* object, so reaching this point means the object existed moments ago.
|
|
322
|
-
* What produces a 403 after a good presign is an expired or clock-skewed
|
|
323
|
-
* signature, a policy change, or a delete that raced the GET — all
|
|
324
|
-
* transient or retryable, none of them "definitively gone".
|
|
325
|
-
*
|
|
326
|
-
* Reporting them as absence is how a store blip becomes data loss:
|
|
327
|
-
* `undefined` on a continuation read is taken as "unknown token", and
|
|
328
|
-
* the engine mints a fresh session under it, forking the thread. The
|
|
329
|
-
* delete race resolves itself on retry, where the presign returns 404
|
|
330
|
-
* and this correctly reads as a miss.
|
|
331
|
-
*/
|
|
332
|
-
async function downloadPresigned(key: string, url: string): Promise<FileSinkBody | undefined> {
|
|
333
|
-
const response = await resolveCursorApiFetch(options)(assertPresignedUrlSafe(options, url), {
|
|
334
|
-
method: "GET",
|
|
335
|
-
redirect: "manual",
|
|
336
|
-
});
|
|
337
|
-
if (response.status === 404) {
|
|
338
|
-
return undefined;
|
|
339
|
-
}
|
|
340
|
-
if (!response.ok) {
|
|
341
|
-
throw new Error(`cursorHostedFiles: download of "${key}" failed (HTTP ${response.status})`);
|
|
342
|
-
}
|
|
343
|
-
const etag = normalizeEtag(response.headers.get("etag"));
|
|
344
|
-
if (etag !== undefined) {
|
|
345
|
-
rememberEtag(key, etag);
|
|
346
|
-
}
|
|
347
|
-
return {
|
|
348
|
-
body: new Uint8Array(await response.arrayBuffer()),
|
|
349
|
-
...(etag === undefined ? {} : { etag }),
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/** One page of the paginated per-directory listing. */
|
|
354
|
-
async function directoryPage(
|
|
355
|
-
files: FilesTarget,
|
|
356
|
-
directory: string,
|
|
357
|
-
pageToken: string | undefined,
|
|
358
|
-
): Promise<ApiFileListing> {
|
|
359
|
-
if (files.wire === "flush") {
|
|
360
|
-
return await flushPost<ApiFileListing>(options, "/list", {
|
|
361
|
-
store: files.store,
|
|
362
|
-
path: directory,
|
|
363
|
-
limit: DIRECTORY_PAGE_SIZE,
|
|
364
|
-
...(pageToken === undefined ? {} : { pageToken }),
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
const query = new URLSearchParams({
|
|
368
|
-
path: directory,
|
|
369
|
-
limit: String(DIRECTORY_PAGE_SIZE),
|
|
370
|
-
});
|
|
371
|
-
if (pageToken !== undefined) {
|
|
372
|
-
query.set("pageToken", pageToken);
|
|
373
|
-
}
|
|
374
|
-
return await storeGet<ApiFileListing>(options, files.path(`?${query.toString()}`));
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/** The unscoped whole-store listing, on whichever wire the target rides. */
|
|
378
|
-
async function unscopedListing(files: FilesTarget): Promise<ApiFileListing> {
|
|
379
|
-
if (files.wire === "flush") {
|
|
380
|
-
return await flushPost<ApiFileListing>(options, "/list", {
|
|
381
|
-
store: files.store,
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
return await storeGet<ApiFileListing>(options, files.path());
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
async function listEntries(prefix: string): Promise<FileSinkEntry[]> {
|
|
388
|
-
return await withFiles(async files => {
|
|
389
|
-
const raw = await unscopedListing(files);
|
|
390
|
-
if (raw.complete === true) {
|
|
391
|
-
const out: FileSinkEntry[] = [];
|
|
392
|
-
for (const file of raw.files ?? []) {
|
|
393
|
-
if (typeof file.path !== "string" || !file.path.startsWith(prefix)) {
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
out.push(toEntry(file, file.path));
|
|
397
|
-
}
|
|
398
|
-
// The FileSink contract promises key order, and transcript-chunk
|
|
399
|
-
// consumers depend on it (chunk keys are zero-padded so key order
|
|
400
|
-
// is append order). The server happens to scan in order today; do
|
|
401
|
-
// not let that be the only thing holding the contract.
|
|
402
|
-
return out.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
403
|
-
}
|
|
404
|
-
// The unscoped listing was truncated (`complete: false`) and has no
|
|
405
|
-
// continuation to follow. `list()` promises the whole scope, so rather
|
|
406
|
-
// than lie or fail, walk the paginated per-directory form, which does
|
|
407
|
-
// paginate and reports immediate subdirectories to recurse into.
|
|
408
|
-
return await walkDirectoryTree(files, prefix);
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Every entry under `prefix`, via the paginated directory listing.
|
|
414
|
-
*
|
|
415
|
-
* Starts at the deepest directory the prefix fully names and descends only
|
|
416
|
-
* into subdirectories that can still hold matches. Bounded so a runaway
|
|
417
|
-
* store fails loudly rather than returning a silently partial answer the
|
|
418
|
-
* caller would mistake for the whole scope.
|
|
419
|
-
*/
|
|
420
|
-
async function walkDirectoryTree(files: FilesTarget, prefix: string): Promise<FileSinkEntry[]> {
|
|
421
|
-
const slash = prefix.lastIndexOf("/");
|
|
422
|
-
const pending: string[] = [slash === -1 ? "" : prefix.slice(0, slash)];
|
|
423
|
-
const queued = new Set<string>(pending);
|
|
424
|
-
const out: FileSinkEntry[] = [];
|
|
425
|
-
let pageBudget = MAX_LIST_WALK_PAGES;
|
|
426
|
-
while (pending.length > 0) {
|
|
427
|
-
const directory = pending.pop() as string;
|
|
428
|
-
let pageToken: string | undefined;
|
|
429
|
-
do {
|
|
430
|
-
if (pageBudget <= 0) {
|
|
431
|
-
throw new Error(
|
|
432
|
-
"cursorHostedFiles: the store is too large to list; scope the listing with a narrower prefix",
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
pageBudget -= 1;
|
|
436
|
-
const page = await directoryPage(files, directory, pageToken);
|
|
437
|
-
for (const file of page.files ?? []) {
|
|
438
|
-
if (typeof file.path === "string" && file.path.startsWith(prefix)) {
|
|
439
|
-
out.push(toEntry(file, file.path));
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
// Every page can surface different subdirectories, so collect from
|
|
443
|
-
// all of them, deduped — and descend only into branches that can
|
|
444
|
-
// still hold matches: a descendant of the prefix, or (at the first
|
|
445
|
-
// level) a directory the prefix's basename component narrows to.
|
|
446
|
-
for (const subdir of page.directories ?? []) {
|
|
447
|
-
if (queued.has(subdir)) {
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
if (`${subdir}/`.startsWith(prefix) || prefix.startsWith(`${subdir}/`)) {
|
|
451
|
-
queued.add(subdir);
|
|
452
|
-
pending.push(subdir);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
pageToken =
|
|
456
|
-
page.complete === false && page.nextPageToken !== undefined
|
|
457
|
-
? page.nextPageToken
|
|
458
|
-
: undefined;
|
|
459
|
-
} while (pageToken !== undefined);
|
|
460
|
-
}
|
|
461
|
-
return out.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/** Shared decode: remember the etag as a side effect, then shape the entry. */
|
|
465
|
-
function toEntry(file: ApiFile, key: string): FileSinkEntry {
|
|
466
|
-
if (typeof file.etag === "string" && file.etag !== "") {
|
|
467
|
-
rememberEtag(key, file.etag);
|
|
468
|
-
}
|
|
469
|
-
const mtimeMs = typeof file.updatedAt === "string" ? Date.parse(file.updatedAt) : undefined;
|
|
470
|
-
return {
|
|
471
|
-
key,
|
|
472
|
-
size: typeof file.sizeBytes === "number" ? file.sizeBytes : 0,
|
|
473
|
-
...(typeof file.etag === "string" && file.etag !== "" ? { etag: file.etag } : {}),
|
|
474
|
-
...(mtimeMs === undefined || Number.isNaN(mtimeMs) ? {} : { mtimeMs }),
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* One key's current entry, via the bounded directory listing.
|
|
480
|
-
*
|
|
481
|
-
* The unscoped listing has no prefix parameter, so using it for a point lookup
|
|
482
|
-
* would walk the whole store on the hot write path and cost more the fuller
|
|
483
|
-
* the store got. `?path=` is scoped to one directory and paginates, and the
|
|
484
|
-
* scan stops as soon as the key appears.
|
|
485
|
-
*/
|
|
486
|
-
async function statKey(key: string): Promise<FileSinkEntry | undefined> {
|
|
487
|
-
const slash = key.lastIndexOf("/");
|
|
488
|
-
const directory = slash === -1 ? "" : key.slice(0, slash);
|
|
489
|
-
return await withFiles(async files => {
|
|
490
|
-
let pageToken: string | undefined;
|
|
491
|
-
for (let page = 0; page < MAX_DIRECTORY_PAGES; page += 1) {
|
|
492
|
-
const raw = await directoryPage(files, directory, pageToken);
|
|
493
|
-
for (const file of raw.files ?? []) {
|
|
494
|
-
if (file.path === key) {
|
|
495
|
-
return toEntry(file, key);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
// `complete` is false exactly when another page is waiting.
|
|
499
|
-
if (raw.complete !== false || raw.nextPageToken === undefined) {
|
|
500
|
-
return undefined;
|
|
501
|
-
}
|
|
502
|
-
pageToken = raw.nextPageToken;
|
|
503
|
-
}
|
|
504
|
-
return undefined;
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/** One key's share of a (possibly multi-file) upload attempt. */
|
|
509
|
-
type UploadSpec = {
|
|
510
|
-
key: string;
|
|
511
|
-
body: Uint8Array;
|
|
512
|
-
precondition: { etag: string } | { new: true };
|
|
513
|
-
};
|
|
514
|
-
type UploadOutcome =
|
|
515
|
-
| { status: "ok"; result: FileSinkPutResult }
|
|
516
|
-
| { status: "conflict" }
|
|
517
|
-
| { status: "error"; error: unknown };
|
|
518
|
-
|
|
519
|
-
/**
|
|
520
|
-
* The shared presign for one upload attempt: a `/v0` upload POST, or one
|
|
521
|
-
* flush request whose put outcomes are reshaped into the same instruction
|
|
522
|
-
* list (an outcome without a `url` — an oversize refusal — surfaces exactly
|
|
523
|
-
* like a `/v0` multipart answer: no single-part instruction for that key).
|
|
524
|
-
*/
|
|
525
|
-
async function presignUploads(
|
|
526
|
-
files: FilesTarget,
|
|
527
|
-
specs: UploadSpec[],
|
|
528
|
-
): Promise<ApiPresignResponse> {
|
|
529
|
-
if (files.wire === "flush") {
|
|
530
|
-
const response = await flushPost<FlushWireResponse>(options, "/flush", {
|
|
531
|
-
store: files.store,
|
|
532
|
-
ops: specs.map(spec => ({
|
|
533
|
-
op: "put",
|
|
534
|
-
path: spec.key,
|
|
535
|
-
sha: createHash("sha256").update(spec.body).digest("hex"),
|
|
536
|
-
size: spec.body.byteLength,
|
|
537
|
-
...spec.precondition,
|
|
538
|
-
})),
|
|
539
|
-
});
|
|
540
|
-
// Index-aligned with the request ops, entries without a `url` included:
|
|
541
|
-
// a refused op (oversize) must keep its slot so the by-path/positional
|
|
542
|
-
// instruction matching below cannot hand its sibling's presign to it,
|
|
543
|
-
// and the missing url surfaces as that key's own error.
|
|
544
|
-
return {
|
|
545
|
-
files: (response.ops ?? []).map(op => ({
|
|
546
|
-
path: op.path,
|
|
547
|
-
...(typeof op.url === "string" && op.url !== "" ? { url: op.url } : {}),
|
|
548
|
-
headers: op.headers,
|
|
549
|
-
})),
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
return await storePost<ApiPresignResponse>(options, files.path("/upload"), {
|
|
553
|
-
files: specs.map(spec => ({
|
|
554
|
-
path: spec.key,
|
|
555
|
-
sha: createHash("sha256").update(spec.body).digest("hex"),
|
|
556
|
-
size: spec.body.byteLength,
|
|
557
|
-
...spec.precondition,
|
|
558
|
-
})),
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* One presign call covering every spec, then parallel presigned PUTs.
|
|
564
|
-
* Outcomes are per spec: a 412 ("conflict") or a failed PUT for one key
|
|
565
|
-
* never disturbs its batch siblings. Only the presign itself is shared —
|
|
566
|
-
* a presign error throws and fails the whole attempt.
|
|
567
|
-
*/
|
|
568
|
-
async function attemptUploads(files: FilesTarget, specs: UploadSpec[]): Promise<UploadOutcome[]> {
|
|
569
|
-
const presigned = await presignUploads(files, specs);
|
|
570
|
-
return await Promise.all(
|
|
571
|
-
specs.map(async (spec, index): Promise<UploadOutcome> => {
|
|
572
|
-
// Match by path, falling back to position for servers that answer
|
|
573
|
-
// in request order without echoing the path.
|
|
574
|
-
const instruction =
|
|
575
|
-
presigned.files?.find(file => file.path === spec.key) ?? presigned.files?.[index];
|
|
576
|
-
if (instruction?.url === undefined) {
|
|
577
|
-
// Files past the single-part threshold come back with a multipart
|
|
578
|
-
// plan instead of a url; this backend does not implement multipart.
|
|
579
|
-
return {
|
|
580
|
-
status: "error",
|
|
581
|
-
error: new Error(
|
|
582
|
-
`cursorHostedFiles: no single-part upload url for "${spec.key}" (too large?)`,
|
|
583
|
-
),
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
try {
|
|
587
|
-
const response = await resolveCursorApiFetch(options)(
|
|
588
|
-
assertPresignedUrlSafe(options, instruction.url),
|
|
589
|
-
{
|
|
590
|
-
method: "PUT",
|
|
591
|
-
headers: instruction.headers ?? {},
|
|
592
|
-
// A view over bytes is a valid fetch body; the DOM lib types
|
|
593
|
-
// are narrower than the runtime, and copying into a Blob would
|
|
594
|
-
// double the memory for every upload.
|
|
595
|
-
body: spec.body as unknown as BodyInit,
|
|
596
|
-
// A redirect would re-send the body somewhere the presign did
|
|
597
|
-
// not authorize.
|
|
598
|
-
redirect: "manual",
|
|
599
|
-
},
|
|
600
|
-
);
|
|
601
|
-
if (response.status === 412) {
|
|
602
|
-
observedEtags.delete(spec.key);
|
|
603
|
-
return { status: "conflict" };
|
|
604
|
-
}
|
|
605
|
-
if (!response.ok) {
|
|
606
|
-
return {
|
|
607
|
-
status: "error",
|
|
608
|
-
error: new Error(
|
|
609
|
-
`cursorHostedFiles: upload of "${spec.key}" failed (HTTP ${response.status})`,
|
|
610
|
-
),
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
const etag = normalizeEtag(response.headers.get("etag"));
|
|
614
|
-
if (etag !== undefined) {
|
|
615
|
-
rememberEtag(spec.key, etag);
|
|
616
|
-
}
|
|
617
|
-
return { status: "ok", result: etag === undefined ? {} : { etag } };
|
|
618
|
-
} catch (error) {
|
|
619
|
-
return { status: "error", error };
|
|
620
|
-
}
|
|
621
|
-
}),
|
|
622
|
-
);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
/**
|
|
626
|
-
* The full single-key write flow: precondition resolution, one shared
|
|
627
|
-
* (already-performed) first attempt outcome, and the conflict handling
|
|
628
|
-
* `put` promises — including the one statKey-and-retry when an optimistic
|
|
629
|
-
* create loses to an existing object.
|
|
630
|
-
*/
|
|
631
|
-
async function settlePut(
|
|
632
|
-
files: FilesTarget,
|
|
633
|
-
entry: {
|
|
634
|
-
key: string;
|
|
635
|
-
body: Uint8Array;
|
|
636
|
-
options?: { ifMatch?: string | null };
|
|
637
|
-
cachedEtagUsed?: boolean;
|
|
638
|
-
staleEtag?: string;
|
|
639
|
-
},
|
|
640
|
-
first: UploadOutcome,
|
|
641
|
-
): Promise<FileSinkPutResult> {
|
|
642
|
-
if (first.status === "error") {
|
|
643
|
-
throw first.error;
|
|
644
|
-
}
|
|
645
|
-
if (first.status === "ok") {
|
|
646
|
-
return first.result;
|
|
647
|
-
}
|
|
648
|
-
// Conflict. Explicit `ifMatch` (etag or create-only) and cached-etag
|
|
649
|
-
// writes are single-attempt: the lost precondition is the caller's.
|
|
650
|
-
const requested = entry.options?.ifMatch;
|
|
651
|
-
if (requested !== undefined) {
|
|
652
|
-
throw new FileConflictError(entry.key, {
|
|
653
|
-
storedEtag: requested ?? undefined,
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
if (entry.cachedEtagUsed === true) {
|
|
657
|
-
// Someone else wrote between our etag observation and this PUT.
|
|
658
|
-
throw new FileConflictError(entry.key, { storedEtag: entry.staleEtag });
|
|
659
|
-
}
|
|
660
|
-
// The optimistic create lost: learn the current etag once and replace.
|
|
661
|
-
const found = await statKey(entry.key);
|
|
662
|
-
if (found?.etag === undefined) {
|
|
663
|
-
// Existed a moment ago (the create lost) but stat sees nothing: a
|
|
664
|
-
// delete raced this write. Surface the conflict; a retry from the
|
|
665
|
-
// caller lands on the fresh create-only path.
|
|
666
|
-
throw new FileConflictError(entry.key, {});
|
|
667
|
-
}
|
|
668
|
-
const [replaced] = await attemptUploads(files, [
|
|
669
|
-
{ key: entry.key, body: entry.body, precondition: { etag: found.etag } },
|
|
670
|
-
]);
|
|
671
|
-
if (replaced === undefined || replaced.status === "conflict") {
|
|
672
|
-
throw new FileConflictError(entry.key, { storedEtag: found?.etag });
|
|
673
|
-
}
|
|
674
|
-
if (replaced.status === "error") {
|
|
675
|
-
throw replaced.error;
|
|
676
|
-
}
|
|
677
|
-
return replaced.result;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
/**
|
|
681
|
-
* Precondition for a write, and whether it came from the etag cache (a
|
|
682
|
-
* cached-etag conflict is terminal; an optimistic-create conflict earns
|
|
683
|
-
* one stat-and-retry).
|
|
684
|
-
*/
|
|
685
|
-
function preconditionFor(entry: { key: string; options?: { ifMatch?: string | null } }): {
|
|
686
|
-
precondition: { etag: string } | { new: true };
|
|
687
|
-
cachedEtagUsed: boolean;
|
|
688
|
-
staleEtag?: string;
|
|
689
|
-
} {
|
|
690
|
-
const requested = entry.options?.ifMatch;
|
|
691
|
-
// The store rejects unconditional writes, so every put carries a
|
|
692
|
-
// precondition. `null` is create-only and an explicit etag is a caller
|
|
693
|
-
// assertion.
|
|
694
|
-
if (requested !== undefined) {
|
|
695
|
-
return {
|
|
696
|
-
precondition: requested === null ? { new: true } : { etag: requested },
|
|
697
|
-
cachedEtagUsed: false,
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
// Unconditional write with a known etag: replace what we saw last.
|
|
701
|
-
const cached = observedEtags.get(entry.key);
|
|
702
|
-
if (cached !== undefined) {
|
|
703
|
-
return {
|
|
704
|
-
precondition: { etag: cached },
|
|
705
|
-
cachedEtagUsed: true,
|
|
706
|
-
staleEtag: cached,
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
|
-
// Unconditional write to a key this process has never observed:
|
|
710
|
-
// create-only first, learning the current etag only on conflict. Most
|
|
711
|
-
// such writes are brand-new keys (session-event chunks mint a fresh key
|
|
712
|
-
// per flush), and the old stat-before-every-put shape made the
|
|
713
|
-
// precondition lookup the single largest source of `agent-serve-files`
|
|
714
|
-
// list traffic — every fresh pod re-listed a directory per key before
|
|
715
|
-
// its first write.
|
|
716
|
-
return { precondition: { new: true }, cachedEtagUsed: false };
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
return {
|
|
720
|
-
// No local copy: bytes live in the store, so `materializedPath()` is
|
|
721
|
-
// `undefined` and the harness reaches files through `host.files`.
|
|
722
|
-
capabilities: { etags: true },
|
|
723
|
-
|
|
724
|
-
async put(key, body, putOptions): Promise<FileSinkPutResult> {
|
|
725
|
-
return await withFiles(async files => {
|
|
726
|
-
const entry = { key, body, options: putOptions };
|
|
727
|
-
const resolved = preconditionFor(entry);
|
|
728
|
-
const [first] = await attemptUploads(files, [
|
|
729
|
-
{ key, body, precondition: resolved.precondition },
|
|
730
|
-
]);
|
|
731
|
-
if (first === undefined) {
|
|
732
|
-
throw new Error(`cursorHostedFiles: upload of "${key}" vanished`);
|
|
733
|
-
}
|
|
734
|
-
return await settlePut(
|
|
735
|
-
files,
|
|
736
|
-
{
|
|
737
|
-
...entry,
|
|
738
|
-
cachedEtagUsed: resolved.cachedEtagUsed,
|
|
739
|
-
staleEtag: resolved.staleEtag,
|
|
740
|
-
},
|
|
741
|
-
first,
|
|
742
|
-
);
|
|
743
|
-
});
|
|
744
|
-
},
|
|
745
|
-
|
|
746
|
-
async putMany(entries) {
|
|
747
|
-
if (entries.length === 0) {
|
|
748
|
-
return [];
|
|
749
|
-
}
|
|
750
|
-
return await withFiles(async files => {
|
|
751
|
-
const resolved = entries.map(entry => ({
|
|
752
|
-
entry,
|
|
753
|
-
...preconditionFor(entry),
|
|
754
|
-
}));
|
|
755
|
-
let firsts: UploadOutcome[];
|
|
756
|
-
try {
|
|
757
|
-
// One presign call for the whole batch — the point of putMany.
|
|
758
|
-
firsts = await attemptUploads(
|
|
759
|
-
files,
|
|
760
|
-
resolved.map(({ entry, precondition }) => ({
|
|
761
|
-
key: entry.key,
|
|
762
|
-
body: entry.body,
|
|
763
|
-
precondition,
|
|
764
|
-
})),
|
|
765
|
-
);
|
|
766
|
-
} catch (error) {
|
|
767
|
-
// The shared presign failed: every entry failed the same way.
|
|
768
|
-
return entries.map(() => ({
|
|
769
|
-
status: "rejected" as const,
|
|
770
|
-
reason: error,
|
|
771
|
-
}));
|
|
772
|
-
}
|
|
773
|
-
return await Promise.all(
|
|
774
|
-
resolved.map(
|
|
775
|
-
async (
|
|
776
|
-
{ entry, cachedEtagUsed, staleEtag },
|
|
777
|
-
index,
|
|
778
|
-
): Promise<PromiseSettledResult<FileSinkPutResult>> => {
|
|
779
|
-
const first = firsts[index];
|
|
780
|
-
if (first === undefined) {
|
|
781
|
-
return {
|
|
782
|
-
status: "rejected",
|
|
783
|
-
reason: new Error(`cursorHostedFiles: upload of "${entry.key}" vanished`),
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
try {
|
|
787
|
-
return {
|
|
788
|
-
status: "fulfilled",
|
|
789
|
-
value: await settlePut(files, { ...entry, cachedEtagUsed, staleEtag }, first),
|
|
790
|
-
};
|
|
791
|
-
} catch (error) {
|
|
792
|
-
return { status: "rejected", reason: error };
|
|
793
|
-
}
|
|
794
|
-
},
|
|
795
|
-
),
|
|
796
|
-
);
|
|
797
|
-
});
|
|
798
|
-
},
|
|
799
|
-
|
|
800
|
-
async get(key): Promise<FileSinkBody | undefined> {
|
|
801
|
-
return await withFiles(async files => {
|
|
802
|
-
// Presigning a read of a path the store has no live object for — never
|
|
803
|
-
// written, or deleted and now holding a tombstone — is a 404 on `/v0`
|
|
804
|
-
// and an absent entry on the flush wire. Either is absence, which
|
|
805
|
-
// `read()` reports as undefined, not a failure.
|
|
806
|
-
let presigned: ApiPresignResponse;
|
|
807
|
-
try {
|
|
808
|
-
presigned =
|
|
809
|
-
files.wire === "flush"
|
|
810
|
-
? await flushPost<ApiPresignResponse>(options, "/read", {
|
|
811
|
-
store: files.store,
|
|
812
|
-
paths: [key],
|
|
813
|
-
})
|
|
814
|
-
: await storePost<ApiPresignResponse>(options, files.path("/download"), {
|
|
815
|
-
paths: [key],
|
|
816
|
-
});
|
|
817
|
-
} catch (error) {
|
|
818
|
-
if (error instanceof StoreApiError && error.status === 404) {
|
|
819
|
-
return undefined;
|
|
820
|
-
}
|
|
821
|
-
throw error;
|
|
822
|
-
}
|
|
823
|
-
const url = presigned.files?.[0]?.url;
|
|
824
|
-
if (url === undefined) {
|
|
825
|
-
return undefined;
|
|
826
|
-
}
|
|
827
|
-
return await downloadPresigned(key, url);
|
|
828
|
-
});
|
|
829
|
-
},
|
|
830
|
-
|
|
831
|
-
async getMany(keys): Promise<Array<FileSinkBody | undefined>> {
|
|
832
|
-
if (keys.length === 0) {
|
|
833
|
-
return [];
|
|
834
|
-
}
|
|
835
|
-
const bodies = await withFiles(async files => {
|
|
836
|
-
if (files.wire !== "flush") {
|
|
837
|
-
// `/v0` presigns 404 the whole batch when any path is absent, so a
|
|
838
|
-
// batched call cannot tell one missing key from a failed request.
|
|
839
|
-
// Bounded per-key gets keep the `/v0` absence semantics exact; the
|
|
840
|
-
// batch win belongs to the flush wire, whose contract is "an absent
|
|
841
|
-
// path is an absent entry" — and whose per-deployment rate limiter
|
|
842
|
-
// is the reason to spend fewer requests in the first place.
|
|
843
|
-
return await mapBounded([...keys], GET_MANY_DOWNLOAD_CONCURRENCY, async key => {
|
|
844
|
-
const presigned = await storePost<ApiPresignResponse>(
|
|
845
|
-
options,
|
|
846
|
-
files.path("/download"),
|
|
847
|
-
{ paths: [key] },
|
|
848
|
-
).catch((error: unknown) => {
|
|
849
|
-
if (error instanceof StoreApiError && error.status === 404) {
|
|
850
|
-
return undefined;
|
|
851
|
-
}
|
|
852
|
-
throw error;
|
|
853
|
-
});
|
|
854
|
-
const url = presigned?.files?.[0]?.url;
|
|
855
|
-
if (url === undefined) {
|
|
856
|
-
return undefined;
|
|
857
|
-
}
|
|
858
|
-
return await downloadPresigned(key, url);
|
|
859
|
-
});
|
|
860
|
-
}
|
|
861
|
-
const urls = new Map<string, string>();
|
|
862
|
-
for (let from = 0; from < keys.length; from += READ_PRESIGN_BATCH_MAX) {
|
|
863
|
-
const chunk = keys.slice(from, from + READ_PRESIGN_BATCH_MAX);
|
|
864
|
-
let presigned: ApiPresignResponse;
|
|
865
|
-
try {
|
|
866
|
-
presigned = await flushPost<ApiPresignResponse>(options, "/read", {
|
|
867
|
-
store: files.store,
|
|
868
|
-
paths: chunk,
|
|
869
|
-
});
|
|
870
|
-
} catch (error) {
|
|
871
|
-
if (error instanceof StoreApiError && error.status === 404) {
|
|
872
|
-
// The read route never 404s per path (absent paths are
|
|
873
|
-
// omitted entries); a 404 is the store itself — not
|
|
874
|
-
// provisioned, or deleted under us — which get() reads as
|
|
875
|
-
// absence for every key.
|
|
876
|
-
return keys.map(() => undefined);
|
|
877
|
-
}
|
|
878
|
-
throw error;
|
|
879
|
-
}
|
|
880
|
-
for (const entry of presigned.files ?? []) {
|
|
881
|
-
if (entry.path !== undefined && entry.url !== undefined) {
|
|
882
|
-
urls.set(entry.path, entry.url);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
return await mapBounded([...keys], GET_MANY_DOWNLOAD_CONCURRENCY, async key => {
|
|
887
|
-
const url = urls.get(key);
|
|
888
|
-
if (url !== undefined) {
|
|
889
|
-
return await downloadPresigned(key, url);
|
|
890
|
-
}
|
|
891
|
-
// A miss in the batch is NOT yet a definitive absence: the
|
|
892
|
-
// server echoes each path *normalized* (trimmed, backslashes
|
|
893
|
-
// folded, empty/"." segments dropped), so a key whose
|
|
894
|
-
// normalized form differs from what was sent never matches the
|
|
895
|
-
// echo even though its object exists. `get()` is immune (it
|
|
896
|
-
// reads the response positionally), so settle every batch miss
|
|
897
|
-
// through the single-path read — keeping getMany's absence
|
|
898
|
-
// semantics byte-identical to get()'s. Genuine absences pay one
|
|
899
|
-
// extra request; every batch caller reads keys it just listed
|
|
900
|
-
// or was told exist, so real misses are rare (delete races,
|
|
901
|
-
// store loss).
|
|
902
|
-
const presigned = await flushPost<ApiPresignResponse>(options, "/read", {
|
|
903
|
-
store: files.store,
|
|
904
|
-
paths: [key],
|
|
905
|
-
}).catch((error: unknown) => {
|
|
906
|
-
// Store-level 404 (deleted between the batch and this read):
|
|
907
|
-
// absence, same as get().
|
|
908
|
-
if (error instanceof StoreApiError && error.status === 404) {
|
|
909
|
-
return undefined;
|
|
910
|
-
}
|
|
911
|
-
throw error;
|
|
912
|
-
});
|
|
913
|
-
const singleUrl = presigned?.files?.[0]?.url;
|
|
914
|
-
if (singleUrl === undefined) {
|
|
915
|
-
return undefined;
|
|
916
|
-
}
|
|
917
|
-
return await downloadPresigned(key, singleUrl);
|
|
918
|
-
});
|
|
919
|
-
});
|
|
920
|
-
return bodies;
|
|
921
|
-
},
|
|
922
|
-
|
|
923
|
-
async delete(key): Promise<void> {
|
|
924
|
-
const handled = await withFiles(async files => {
|
|
925
|
-
if (files.wire !== "flush") {
|
|
926
|
-
return false;
|
|
927
|
-
}
|
|
928
|
-
// With an observed etag the flush delete is If-Match on that version.
|
|
929
|
-
// Without one, the server tombstones whatever live object occupies
|
|
930
|
-
// the key (no directory walk here) and an absent key settles as
|
|
931
|
-
// `already_deleted`.
|
|
932
|
-
const cached = observedEtags.get(key);
|
|
933
|
-
const response = await flushPost<FlushWireResponse>(options, "/flush", {
|
|
934
|
-
store: files.store,
|
|
935
|
-
ops: [
|
|
936
|
-
{
|
|
937
|
-
op: "delete",
|
|
938
|
-
path: key,
|
|
939
|
-
...(cached === undefined ? {} : { etag: cached }),
|
|
940
|
-
},
|
|
941
|
-
],
|
|
942
|
-
});
|
|
943
|
-
const outcome = response.ops?.[0];
|
|
944
|
-
if (outcome?.status === "conflict") {
|
|
945
|
-
observedEtags.delete(key);
|
|
946
|
-
throw new FileConflictError(key, {
|
|
947
|
-
...(outcome.etag === undefined
|
|
948
|
-
? {}
|
|
949
|
-
: { storedEtag: normalizeEtag(outcome.etag) ?? outcome.etag }),
|
|
950
|
-
});
|
|
951
|
-
}
|
|
952
|
-
observedEtags.delete(key);
|
|
953
|
-
return true;
|
|
954
|
-
});
|
|
955
|
-
if (handled) {
|
|
956
|
-
return;
|
|
957
|
-
}
|
|
958
|
-
if (observedEtags.get(key) === undefined) {
|
|
959
|
-
// Deletes are conditional too, so learn the current etag first. A key
|
|
960
|
-
// we have never seen is already absent as far as the caller cares.
|
|
961
|
-
const found = await statKey(key);
|
|
962
|
-
if (found?.etag === undefined) {
|
|
963
|
-
return;
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
const etag = observedEtags.get(key);
|
|
967
|
-
if (etag === undefined) {
|
|
968
|
-
return;
|
|
969
|
-
}
|
|
970
|
-
await withFiles(async files => {
|
|
971
|
-
if (files.wire === "flush") {
|
|
972
|
-
// The protocol pin appeared between the two withFiles calls; the
|
|
973
|
-
// cached etag is enough for the conditional flush delete.
|
|
974
|
-
const response = await flushPost<FlushWireResponse>(options, "/flush", {
|
|
975
|
-
store: files.store,
|
|
976
|
-
ops: [{ op: "delete", path: key, etag }],
|
|
977
|
-
});
|
|
978
|
-
const outcome = response.ops?.[0];
|
|
979
|
-
if (outcome?.status === "conflict") {
|
|
980
|
-
observedEtags.delete(key);
|
|
981
|
-
throw new FileConflictError(key, {
|
|
982
|
-
...(outcome.etag === undefined
|
|
983
|
-
? {}
|
|
984
|
-
: { storedEtag: normalizeEtag(outcome.etag) ?? outcome.etag }),
|
|
985
|
-
});
|
|
986
|
-
}
|
|
987
|
-
return;
|
|
988
|
-
}
|
|
989
|
-
const raw = await storePost<ApiDeleteResponse>(options, files.path("/delete"), {
|
|
990
|
-
files: [{ path: key, etag }],
|
|
991
|
-
});
|
|
992
|
-
// The API answers 200 with a per-file status, so a lost race would look
|
|
993
|
-
// like success unless the outcome is read.
|
|
994
|
-
const outcome = (raw.files ?? []).find(entry => entry.path === key);
|
|
995
|
-
if (outcome?.status === "conflict") {
|
|
996
|
-
observedEtags.delete(key);
|
|
997
|
-
throw new FileConflictError(key, {
|
|
998
|
-
...(outcome.etag === undefined
|
|
999
|
-
? {}
|
|
1000
|
-
: { storedEtag: normalizeEtag(outcome.etag) ?? outcome.etag }),
|
|
1001
|
-
});
|
|
1002
|
-
}
|
|
1003
|
-
});
|
|
1004
|
-
observedEtags.delete(key);
|
|
1005
|
-
},
|
|
1006
|
-
|
|
1007
|
-
list: listEntries,
|
|
1008
|
-
|
|
1009
|
-
async stat(key): Promise<FileSinkEntry | undefined> {
|
|
1010
|
-
return await statKey(key);
|
|
1011
|
-
},
|
|
1012
|
-
};
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
/**
|
|
1016
|
-
* Refuse a presigned URL that does not target this backend's agent-stores
|
|
1017
|
-
* bucket.
|
|
1018
|
-
*
|
|
1019
|
-
* The URL arrives from BCS over TLS, so this is defense in depth rather than
|
|
1020
|
-
* a live hole — but the engine pod can reach link-local metadata and other
|
|
1021
|
-
* allowlisted egress hosts, so a BCS bug or compromise returning a foreign
|
|
1022
|
-
* address would otherwise steer file bytes somewhere the caller never chose.
|
|
1023
|
-
* Same pin as the sibling sync client: production backends allow only the
|
|
1024
|
-
* agent-stores bucket hosts, playground its bucket, and a local dev backend
|
|
1025
|
-
* additionally admits loopback http for LocalStack.
|
|
1026
|
-
*
|
|
1027
|
-
* `CURSOR_HOSTED_FILES_DEV_PRESIGN_HOSTS` (comma-separated hostnames) admits
|
|
1028
|
-
* extra https hosts. A laptop backend hosting agents on a shared cluster
|
|
1029
|
-
* fronts its LocalStack with a public tunnel and exports this in the pod's
|
|
1030
|
-
* start script; production start scripts never set it. Read once at module
|
|
1031
|
-
* load, so authored agent code sharing this process cannot widen the list
|
|
1032
|
-
* by mutating `process.env` later.
|
|
1033
|
-
*/
|
|
1034
|
-
function assertPresignedUrlSafe(options: CursorHostedFilesOptions, raw: string): string {
|
|
1035
|
-
let url: URL;
|
|
1036
|
-
try {
|
|
1037
|
-
url = new URL(raw);
|
|
1038
|
-
} catch {
|
|
1039
|
-
throw new Error("cursorHostedFiles: presigned URL is not a valid URL");
|
|
1040
|
-
}
|
|
1041
|
-
if (
|
|
1042
|
-
url.protocol === "https:" &&
|
|
1043
|
-
url.username === "" &&
|
|
1044
|
-
url.password === "" &&
|
|
1045
|
-
DEV_PRESIGN_HOSTS.has(url.hostname.toLowerCase())
|
|
1046
|
-
) {
|
|
1047
|
-
return url.toString();
|
|
1048
|
-
}
|
|
1049
|
-
try {
|
|
1050
|
-
createAgentStorePresignedUrlValidatorForBackend(resolveCursorApiBaseUrl(options))(url);
|
|
1051
|
-
} catch (error) {
|
|
1052
|
-
throw new Error(
|
|
1053
|
-
`cursorHostedFiles: refused presigned URL: ${
|
|
1054
|
-
error instanceof Error ? error.message : String(error)
|
|
1055
|
-
}`,
|
|
1056
|
-
);
|
|
1057
|
-
}
|
|
1058
|
-
return url.toString();
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
/**
|
|
1062
|
-
* The file store this engine's deployment owns. Every request names it, so
|
|
1063
|
-
* the server resolves the store row and this process never handles a store id.
|
|
1064
|
-
*
|
|
1065
|
-
* Fails loudly rather than defaulting: with no source id there is no store to
|
|
1066
|
-
* address, and writing somewhere else silently is worse than not writing.
|
|
1067
|
-
*/
|
|
1068
|
-
function resolveStoreSourceId(options: CursorHostedFilesOptions): string {
|
|
1069
|
-
const id = (
|
|
1070
|
-
options.resolveStoreSourceId?.() ??
|
|
1071
|
-
options.storeSourceId ??
|
|
1072
|
-
resolveDeploymentStoreSourceIdSync(process.env)
|
|
1073
|
-
)?.trim();
|
|
1074
|
-
if (id === undefined || id === "") {
|
|
1075
|
-
throw new Error(
|
|
1076
|
-
`cursorHostedFiles requires ${AGENT_SERVE_STORE_SOURCE_ID_ENV_VAR} (set on Cursor-managed deployments)`,
|
|
1077
|
-
);
|
|
1078
|
-
}
|
|
1079
|
-
return id;
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
const DEV_PRESIGN_HOSTS: ReadonlySet<string> = new Set(
|
|
1083
|
-
(process.env.CURSOR_HOSTED_FILES_DEV_PRESIGN_HOSTS ?? "")
|
|
1084
|
-
.split(",")
|
|
1085
|
-
.map(host => host.trim().toLowerCase())
|
|
1086
|
-
.filter(host => host.length > 0),
|
|
1087
|
-
);
|
|
1088
|
-
|
|
1089
|
-
/**
|
|
1090
|
-
* Public agent-serve-files API call. The pod credential is the whole authorization:
|
|
1091
|
-
* the API forwards this caller's identity to the store service, which checks it
|
|
1092
|
-
* against the store's live owner. There is no second, narrower credential.
|
|
1093
|
-
*/
|
|
1094
|
-
async function storePost<T>(
|
|
1095
|
-
options: CursorHostedFilesOptions,
|
|
1096
|
-
path: string,
|
|
1097
|
-
body: Record<string, unknown>,
|
|
1098
|
-
): Promise<T> {
|
|
1099
|
-
return await postJson<T>(
|
|
1100
|
-
options,
|
|
1101
|
-
`${resolveCursorApiBaseUrl(options)}${AGENT_SERVE_FILES_PATH}${path}`,
|
|
1102
|
-
body,
|
|
1103
|
-
);
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
async function storeGet<T>(options: CursorHostedFilesOptions, path: string): Promise<T> {
|
|
1107
|
-
return await sendJson<T>(
|
|
1108
|
-
options,
|
|
1109
|
-
`${resolveCursorApiBaseUrl(options)}${AGENT_SERVE_FILES_PATH}${path}`,
|
|
1110
|
-
"GET",
|
|
1111
|
-
);
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
/**
|
|
1115
|
-
* Factory store API call — same base URL and pod credential as
|
|
1116
|
-
* {@link storePost}, different path family. The server resolves the
|
|
1117
|
-
* deployment (and the addressed store role) from the credential. The
|
|
1118
|
-
* domain-call lane one path family deeper is NOT here: it belongs to the
|
|
1119
|
-
* store API client (`../internal/cursor/store-api-client.ts`).
|
|
1120
|
-
*/
|
|
1121
|
-
async function flushPost<T>(
|
|
1122
|
-
options: CursorHostedFilesOptions,
|
|
1123
|
-
path: string,
|
|
1124
|
-
body: Record<string, unknown>,
|
|
1125
|
-
): Promise<T> {
|
|
1126
|
-
return await postJson<T>(
|
|
1127
|
-
options,
|
|
1128
|
-
`${resolveCursorApiBaseUrl(options)}${AGENTSDK_STORE_PATH}${path}`,
|
|
1129
|
-
body,
|
|
1130
|
-
);
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
async function postJson<T>(
|
|
1134
|
-
options: CursorHostedFilesOptions,
|
|
1135
|
-
url: string,
|
|
1136
|
-
body: Record<string, unknown>,
|
|
1137
|
-
): Promise<T> {
|
|
1138
|
-
return await sendJson<T>(options, url, "POST", body);
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
async function sendJson<T>(
|
|
1142
|
-
options: CursorHostedFilesOptions,
|
|
1143
|
-
url: string,
|
|
1144
|
-
method: "GET" | "POST",
|
|
1145
|
-
body?: Record<string, unknown>,
|
|
1146
|
-
): Promise<T> {
|
|
1147
|
-
const response = await resolveCursorApiFetch(options)(url, {
|
|
1148
|
-
method,
|
|
1149
|
-
headers: {
|
|
1150
|
-
Authorization: `Bearer ${resolveCursorApiKey(options)}`,
|
|
1151
|
-
...(body === undefined ? {} : { "Content-Type": "application/json" }),
|
|
1152
|
-
},
|
|
1153
|
-
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
1154
|
-
});
|
|
1155
|
-
const text = await response.text();
|
|
1156
|
-
let parsed: unknown;
|
|
1157
|
-
try {
|
|
1158
|
-
parsed = text.length === 0 ? {} : JSON.parse(text);
|
|
1159
|
-
} catch {
|
|
1160
|
-
throw new Error(`cursorHostedFiles ${url} returned non-JSON (${response.status})`);
|
|
1161
|
-
}
|
|
1162
|
-
if (!response.ok) {
|
|
1163
|
-
throw new StoreApiError(
|
|
1164
|
-
response.status,
|
|
1165
|
-
`cursorHostedFiles ${url}: ${describeError(parsed, response.status)}`,
|
|
1166
|
-
);
|
|
1167
|
-
}
|
|
1168
|
-
return parsed as T;
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
/**
|
|
1172
|
-
* A non-2xx answer from the files API, carrying the status so a caller can
|
|
1173
|
-
* tell "absent" apart from a failure it must not swallow.
|
|
1174
|
-
*/
|
|
1175
|
-
class StoreApiError extends Error {
|
|
1176
|
-
constructor(
|
|
1177
|
-
readonly status: number,
|
|
1178
|
-
message: string,
|
|
1179
|
-
) {
|
|
1180
|
-
super(message);
|
|
1181
|
-
this.name = "StoreApiError";
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
function describeError(parsed: unknown, status: number): string {
|
|
1186
|
-
if (
|
|
1187
|
-
parsed !== null &&
|
|
1188
|
-
typeof parsed === "object" &&
|
|
1189
|
-
"error" in parsed &&
|
|
1190
|
-
typeof (parsed as { error: unknown }).error === "string"
|
|
1191
|
-
) {
|
|
1192
|
-
return (parsed as { error: string }).error;
|
|
1193
|
-
}
|
|
1194
|
-
if (
|
|
1195
|
-
parsed !== null &&
|
|
1196
|
-
typeof parsed === "object" &&
|
|
1197
|
-
"message" in parsed &&
|
|
1198
|
-
typeof (parsed as { message: unknown }).message === "string"
|
|
1199
|
-
) {
|
|
1200
|
-
return (parsed as { message: string }).message;
|
|
1201
|
-
}
|
|
1202
|
-
return `HTTP ${status}`;
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
/** S3 returns quoted etags; the store compares them unquoted. */
|
|
1206
|
-
function normalizeEtag(raw: string | null): string | undefined {
|
|
1207
|
-
if (raw === null) {
|
|
1208
|
-
return undefined;
|
|
1209
|
-
}
|
|
1210
|
-
const trimmed = raw.trim().replace(/^"+|"+$/g, "");
|
|
1211
|
-
return trimmed === "" ? undefined : trimmed;
|
|
1212
|
-
}
|