@ebrindley/build 0.0.0-stage → 1.42.1-rc28
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/.build-project/providers/catalog.json +13951 -0
- package/.build-project/providers/pricing.json +1978 -0
- package/.build-project/providers/registry.json +4103 -0
- package/.github/SECURITY.md +75 -0
- package/.nvmrc +1 -0
- package/CHANGELOG.md +661 -0
- package/CODE_OF_CONDUCT.md +44 -0
- package/CONTRIBUTING.md +24 -0
- package/INSTALL.md +623 -0
- package/LICENSE +21 -0
- package/README.md +118 -2
- package/THIRD_PARTY_NOTICES.txt +26 -0
- package/dist/ai-judge-UE5UOYOO.js +191 -0
- package/dist/allowlist-grounding-KPLN6NC5.js +16 -0
- package/dist/allowlist-utils-MZZKKXRD.js +31 -0
- package/dist/apply-completion-mode-EM37UZHB.js +110 -0
- package/dist/artifact-migrator-WPACNSEP.js +276 -0
- package/dist/ask-2OZ4JFQR.js +176 -0
- package/dist/auth-6UKBTJUQ.js +185 -0
- package/dist/auth-H5KJL6HL.js +165 -0
- package/dist/auth-LVJRJDMU.js +12 -0
- package/dist/auth-NHZOCNRR.js +94 -0
- package/dist/auth-PBXHWXGW.js +95 -0
- package/dist/auth-T7Y5DCAH.js +91 -0
- package/dist/auth-TVVKCHQW.js +13 -0
- package/dist/auth-UHB2UCSS.js +97 -0
- package/dist/auth-VACA3VUL.js +98 -0
- package/dist/auth-VVLC77S3.js +44 -0
- package/dist/auth-liveness-XO4AGDDD.js +53 -0
- package/dist/autoloop-cleanup-SI3VGAAR.js +106 -0
- package/dist/autoloop-evidence-gates-J5GG5YQB.js +39 -0
- package/dist/autoloop-gate-policy-SUNAEP6Q.js +169 -0
- package/dist/autoloop-helpers-VQGQNTT6.js +52 -0
- package/dist/autoloop-ledger-P6TIDP7C.js +96 -0
- package/dist/autoloop-ledger-subscriber-HFAGOZXH.js +161 -0
- package/dist/autoloop-run-defaults-GWGZBNT7.js +27 -0
- package/dist/autoloop-run-lock-GBWRM5A2.js +197 -0
- package/dist/autoloop-success-C32QVWOP.js +93 -0
- package/dist/autoloop-termination-summary-GI6IBQWD.js +58 -0
- package/dist/autoloop-trajectory-AMKSQISD.js +70 -0
- package/dist/autoloop-wall-clock-T7ZVIQOC.js +12 -0
- package/dist/automatic-branch-cleanup-R62X2C5O.js +49 -0
- package/dist/autonomous-loop-controller-2WL363LW.js +4386 -0
- package/dist/backend-YUTFDZXZ.js +30 -0
- package/dist/background-executor-Z3ODTMD7.js +260 -0
- package/dist/backlog-75QPEAMW.js +403 -0
- package/dist/backlog-execution-intent-GWUV5TUK.js +19 -0
- package/dist/backup-active-store-XHWLN34M.js +16 -0
- package/dist/backup-machine-stores-UI4LTJOY.js +251 -0
- package/dist/backup-telemetry-db-Z4ICYCNW.js +26 -0
- package/dist/branch-archive-manager-FMSQ7YJF.js +18 -0
- package/dist/branch-cleanup-manager-ST6FJGND.js +35 -0
- package/dist/build-gate-7NKWPY62.js +68 -0
- package/dist/build-root-S7DINODY.js +18 -0
- package/dist/change-summary-R6CB6VXJ.js +50 -0
- package/dist/chunk-24MNKKOM.js +173 -0
- package/dist/chunk-2H6VZFIO.js +235 -0
- package/dist/chunk-2JIX3EKV.js +39 -0
- package/dist/chunk-2KO7HVZM.js +106 -0
- package/dist/chunk-2L36U25O.js +100 -0
- package/dist/chunk-2NMNFUQA.js +475 -0
- package/dist/chunk-2OUAEH67.js +83 -0
- package/dist/chunk-2VLJUXJU.js +115 -0
- package/dist/chunk-2VUEDIUM.js +77 -0
- package/dist/chunk-2Y2IIHPI.js +430 -0
- package/dist/chunk-2YRG3LHZ.js +220 -0
- package/dist/chunk-2Z5Q3SUY.js +65 -0
- package/dist/chunk-32IYXIE5.js +873 -0
- package/dist/chunk-33IGPPUI.js +292 -0
- package/dist/chunk-352PCJLK.js +283 -0
- package/dist/chunk-35J4NE6S.js +226 -0
- package/dist/chunk-3C7WBIVK.js +1016 -0
- package/dist/chunk-3H3EY6U7.js +1225 -0
- package/dist/chunk-3H4MWYIC.js +198 -0
- package/dist/chunk-3HHWQR3N.js +2479 -0
- package/dist/chunk-3JKNRYEE.js +14 -0
- package/dist/chunk-3LECCYZY.js +128 -0
- package/dist/chunk-3LOY6YZI.js +852 -0
- package/dist/chunk-3M57DADF.js +44 -0
- package/dist/chunk-3NOTLVLG.js +3803 -0
- package/dist/chunk-3OPP2OZB.js +1480 -0
- package/dist/chunk-3P5IN5E7.js +9 -0
- package/dist/chunk-3PL22IYZ.js +20 -0
- package/dist/chunk-3REDAJUJ.js +14 -0
- package/dist/chunk-3S2R4XJI.js +203 -0
- package/dist/chunk-3UBZI5DW.js +61 -0
- package/dist/chunk-3VV5OBGG.js +308 -0
- package/dist/chunk-3WY5TU2O.js +823 -0
- package/dist/chunk-43OKHTW4.js +82 -0
- package/dist/chunk-45KUJBDI.js +112 -0
- package/dist/chunk-47X5LM7B.js +224 -0
- package/dist/chunk-4B6FQJ77.js +268 -0
- package/dist/chunk-4BTDUT2S.js +42 -0
- package/dist/chunk-4DJL2VXA.js +24 -0
- package/dist/chunk-4EBSXZ73.js +48 -0
- package/dist/chunk-4QKP6I4C.js +294 -0
- package/dist/chunk-4U6XK2DG.js +300 -0
- package/dist/chunk-4UMRUFBD.js +132 -0
- package/dist/chunk-4US4SYJ4.js +569 -0
- package/dist/chunk-4ZPLWEZO.js +154 -0
- package/dist/chunk-56WPYDJH.js +624 -0
- package/dist/chunk-57WRCJJW.js +50 -0
- package/dist/chunk-5AW4RMNG.js +24 -0
- package/dist/chunk-5EQN56OZ.js +155 -0
- package/dist/chunk-5F43TUIN.js +38 -0
- package/dist/chunk-5FKHIKTS.js +115 -0
- package/dist/chunk-5HDP7XZG.js +91 -0
- package/dist/chunk-5KQETTNS.js +152 -0
- package/dist/chunk-5PDVN2KO.js +498 -0
- package/dist/chunk-5UBIFVRD.js +509 -0
- package/dist/chunk-5YDE3Q62.js +46 -0
- package/dist/chunk-626AJVMU.js +302 -0
- package/dist/chunk-633XZ7UF.js +3583 -0
- package/dist/chunk-63DHNHGA.js +236 -0
- package/dist/chunk-666656AR.js +568 -0
- package/dist/chunk-67YRJPJO.js +19 -0
- package/dist/chunk-6CAU7VBA.js +20 -0
- package/dist/chunk-6CB3KJRY.js +21 -0
- package/dist/chunk-6DBMALXL.js +29 -0
- package/dist/chunk-6E25C4IA.js +269 -0
- package/dist/chunk-6FIBNIQU.js +73 -0
- package/dist/chunk-6G57FYBV.js +189 -0
- package/dist/chunk-6KZKRTG3.js +4885 -0
- package/dist/chunk-6LAH2UV2.js +525 -0
- package/dist/chunk-6PZY2TTU.js +921 -0
- package/dist/chunk-6T6NHXK4.js +230 -0
- package/dist/chunk-6TBEX4R4.js +432 -0
- package/dist/chunk-6TRD7TEE.js +911 -0
- package/dist/chunk-6XCP2RAI.js +47 -0
- package/dist/chunk-726G6WDL.js +351 -0
- package/dist/chunk-75EMT7BF.js +616 -0
- package/dist/chunk-762ACDZJ.js +4696 -0
- package/dist/chunk-77I6G4CO.js +1 -0
- package/dist/chunk-7DJ6SQH3.js +510 -0
- package/dist/chunk-7HPCIOMA.js +1383 -0
- package/dist/chunk-7HYBSRPZ.js +17503 -0
- package/dist/chunk-7KBEOPVB.js +73 -0
- package/dist/chunk-7LHJDJB3.js +2222 -0
- package/dist/chunk-7NVJMH52.js +306 -0
- package/dist/chunk-7RMARDVH.js +33 -0
- package/dist/chunk-7SEWU4QG.js +36 -0
- package/dist/chunk-7TUTZTI2.js +911 -0
- package/dist/chunk-7VBDFPUM.js +340 -0
- package/dist/chunk-7VRJHTGZ.js +6406 -0
- package/dist/chunk-7YAOUUKK.js +493 -0
- package/dist/chunk-AAAODV76.js +860 -0
- package/dist/chunk-AB4HUJVQ.js +339 -0
- package/dist/chunk-AEQWV746.js +611 -0
- package/dist/chunk-AHU3FKPA.js +11639 -0
- package/dist/chunk-AISTV5XU.js +226 -0
- package/dist/chunk-AJBTKGJZ.js +48 -0
- package/dist/chunk-AJJNW7XT.js +248 -0
- package/dist/chunk-AJN5Y5YZ.js +160 -0
- package/dist/chunk-ALDTFNZ3.js +150 -0
- package/dist/chunk-ARARVIZK.js +281 -0
- package/dist/chunk-ATBM6AW5.js +53 -0
- package/dist/chunk-ATKEKM3E.js +206 -0
- package/dist/chunk-AUJLW6JZ.js +92 -0
- package/dist/chunk-AY3MHEFM.js +24 -0
- package/dist/chunk-B7PYTV72.js +236 -0
- package/dist/chunk-B7YRSULE.js +1423 -0
- package/dist/chunk-BAWJNMWF.js +339 -0
- package/dist/chunk-BBPIILO2.js +88 -0
- package/dist/chunk-BD3PGW6A.js +957 -0
- package/dist/chunk-BDPYFAB4.js +44 -0
- package/dist/chunk-BIUV23HH.js +729 -0
- package/dist/chunk-BQ3VAGXH.js +243 -0
- package/dist/chunk-BQJOGAWT.js +31 -0
- package/dist/chunk-BT7EG7CO.js +68 -0
- package/dist/chunk-BV3XQQLU.js +27 -0
- package/dist/chunk-BY6ODHNZ.js +78 -0
- package/dist/chunk-BZRLSZNV.js +79 -0
- package/dist/chunk-C3DH3NCY.js +2773 -0
- package/dist/chunk-C3KCMAJP.js +756 -0
- package/dist/chunk-C7AGUJ5K.js +267 -0
- package/dist/chunk-CBSWKQCX.js +342 -0
- package/dist/chunk-CE5OZBYY.js +21 -0
- package/dist/chunk-CFCVYHSW.js +373 -0
- package/dist/chunk-CHA2HDE5.js +126 -0
- package/dist/chunk-CIHWPGS5.js +146 -0
- package/dist/chunk-CJBSZX3F.js +127 -0
- package/dist/chunk-CLA6QIJM.js +43 -0
- package/dist/chunk-CPSVQR2I.js +365 -0
- package/dist/chunk-CRPRVNQ4.js +254 -0
- package/dist/chunk-CSIE4Y7C.js +1093 -0
- package/dist/chunk-CSOPSYDY.js +142 -0
- package/dist/chunk-CSXLLEGB.js +205 -0
- package/dist/chunk-CTDLERWU.js +293 -0
- package/dist/chunk-CULBXMSK.js +110 -0
- package/dist/chunk-D2M2Y5ID.js +21 -0
- package/dist/chunk-D37WY7SH.js +1060 -0
- package/dist/chunk-D4VJFCGZ.js +89 -0
- package/dist/chunk-D6WTZMZB.js +48 -0
- package/dist/chunk-DBHOWDUH.js +976 -0
- package/dist/chunk-DCACX7BV.js +19364 -0
- package/dist/chunk-DFHX4XYA.js +128 -0
- package/dist/chunk-DIKBRRKE.js +176 -0
- package/dist/chunk-DJG5RIOR.js +9 -0
- package/dist/chunk-DNIOIISW.js +616 -0
- package/dist/chunk-DOA5G7IA.js +315 -0
- package/dist/chunk-DP7FKP6W.js +855 -0
- package/dist/chunk-DS2JZ677.js +79 -0
- package/dist/chunk-DSRLVYBS.js +82 -0
- package/dist/chunk-EAEVKNJA.js +209 -0
- package/dist/chunk-EEUESN3T.js +575 -0
- package/dist/chunk-EJ4LUU5Y.js +73 -0
- package/dist/chunk-EJIWTPSZ.js +1063 -0
- package/dist/chunk-EJVCDTAN.js +116 -0
- package/dist/chunk-EKLUBDBQ.js +627 -0
- package/dist/chunk-EN62WLFD.js +849 -0
- package/dist/chunk-ENYRCL43.js +26 -0
- package/dist/chunk-EP22ZMFO.js +273 -0
- package/dist/chunk-EP4SVAVI.js +87 -0
- package/dist/chunk-EPAMVGI3.js +1562 -0
- package/dist/chunk-EPRMH6HO.js +24 -0
- package/dist/chunk-ES6NWJ36.js +4057 -0
- package/dist/chunk-ETA2K4KI.js +30 -0
- package/dist/chunk-ETAD4NKD.js +31 -0
- package/dist/chunk-EUWIIDQ7.js +93 -0
- package/dist/chunk-EVE3OD2W.js +141 -0
- package/dist/chunk-EXA26FFC.js +459 -0
- package/dist/chunk-EXSPAA35.js +244 -0
- package/dist/chunk-F55YABKV.js +258 -0
- package/dist/chunk-F5DNGU5W.js +27 -0
- package/dist/chunk-F622MJYV.js +103 -0
- package/dist/chunk-F6NIQD74.js +5111 -0
- package/dist/chunk-FAWJTC34.js +43 -0
- package/dist/chunk-FAZBRHGG.js +47 -0
- package/dist/chunk-FIEUSGNJ.js +39 -0
- package/dist/chunk-FKCG7K6H.js +1146 -0
- package/dist/chunk-FOVVXV45.js +229 -0
- package/dist/chunk-FRBPKBFE.js +30 -0
- package/dist/chunk-FSA5BPFI.js +268 -0
- package/dist/chunk-FSJ5TTZ3.js +29 -0
- package/dist/chunk-FT7SGLXT.js +193 -0
- package/dist/chunk-FUUVZZUB.js +62 -0
- package/dist/chunk-FYMRVUNZ.js +1697 -0
- package/dist/chunk-FYTAA5FG.js +162 -0
- package/dist/chunk-G3VB2UCZ.js +3892 -0
- package/dist/chunk-G6OIMVXH.js +405 -0
- package/dist/chunk-G7PQCXVH.js +228 -0
- package/dist/chunk-GAB6UW52.js +1087 -0
- package/dist/chunk-GAFJEYLK.js +29 -0
- package/dist/chunk-GC4CRMTS.js +642 -0
- package/dist/chunk-GFHSIWHL.js +5297 -0
- package/dist/chunk-GI5M67YA.js +735 -0
- package/dist/chunk-GIJWHUWF.js +191 -0
- package/dist/chunk-GO6DRECI.js +1 -0
- package/dist/chunk-GOFVBTMN.js +314 -0
- package/dist/chunk-GQIP72ZJ.js +13 -0
- package/dist/chunk-GQXLXRFA.js +28 -0
- package/dist/chunk-GT6HFKJB.js +48 -0
- package/dist/chunk-GVJGSSKM.js +174 -0
- package/dist/chunk-GWHGTG37.js +36 -0
- package/dist/chunk-GZAJHLKO.js +18 -0
- package/dist/chunk-GZK62I3N.js +314 -0
- package/dist/chunk-H3HGMISG.js +483 -0
- package/dist/chunk-H3ISZJMJ.js +403 -0
- package/dist/chunk-H4HXVWEC.js +9 -0
- package/dist/chunk-H6MJGKAW.js +120 -0
- package/dist/chunk-HBTTGVR5.js +353 -0
- package/dist/chunk-HBZ322WZ.js +35 -0
- package/dist/chunk-HEP3ILP4.js +34 -0
- package/dist/chunk-HGFXODKO.js +106 -0
- package/dist/chunk-HHWDTB6S.js +59 -0
- package/dist/chunk-HKO45NLZ.js +286 -0
- package/dist/chunk-HLVTEA6A.js +89 -0
- package/dist/chunk-HS3SY6HN.js +160 -0
- package/dist/chunk-HT5OCGKN.js +899 -0
- package/dist/chunk-HXIIEMGF.js +78 -0
- package/dist/chunk-I56TH7VM.js +46 -0
- package/dist/chunk-I6FN3R5D.js +352 -0
- package/dist/chunk-I7SCBHJN.js +655 -0
- package/dist/chunk-IJM4GKAT.js +529 -0
- package/dist/chunk-IMEYU36R.js +344 -0
- package/dist/chunk-INJ3SS6A.js +936 -0
- package/dist/chunk-IUKOPCCE.js +441 -0
- package/dist/chunk-IVEPZQJO.js +33 -0
- package/dist/chunk-IVZEONFC.js +43 -0
- package/dist/chunk-IWBHRSG7.js +936 -0
- package/dist/chunk-IYY3HQAD.js +484 -0
- package/dist/chunk-J2ICMYDA.js +48 -0
- package/dist/chunk-J2QCFLUN.js +505 -0
- package/dist/chunk-J5HM6V5P.js +1039 -0
- package/dist/chunk-J6NZX6QH.js +398 -0
- package/dist/chunk-JBBB2Y47.js +432 -0
- package/dist/chunk-JDCZQZU7.js +42 -0
- package/dist/chunk-JEDNXGSS.js +169 -0
- package/dist/chunk-JHP7OT6K.js +89 -0
- package/dist/chunk-JLS65NUQ.js +14 -0
- package/dist/chunk-JNMWYMC6.js +38 -0
- package/dist/chunk-JNSTWNNW.js +25 -0
- package/dist/chunk-JOSK6GEU.js +9564 -0
- package/dist/chunk-JQUMSIGC.js +100 -0
- package/dist/chunk-JVHGMURQ.js +1106 -0
- package/dist/chunk-JYXS47FQ.js +951 -0
- package/dist/chunk-K2ZMW2Z5.js +2147 -0
- package/dist/chunk-K7FAFORM.js +14 -0
- package/dist/chunk-KBJXX3ON.js +333 -0
- package/dist/chunk-KHFKVQWY.js +486 -0
- package/dist/chunk-KHMEI3DQ.js +173 -0
- package/dist/chunk-KHW3UD7B.js +69 -0
- package/dist/chunk-KKQKWAMF.js +365 -0
- package/dist/chunk-KOKXJV2U.js +455 -0
- package/dist/chunk-KQ4NGOJI.js +14 -0
- package/dist/chunk-KQYQOXMS.js +342 -0
- package/dist/chunk-KTMW65UX.js +14 -0
- package/dist/chunk-KUEWV2JH.js +593 -0
- package/dist/chunk-KXYDV3HP.js +1523 -0
- package/dist/chunk-KZ4JMGZ5.js +96 -0
- package/dist/chunk-KZWGBUIO.js +2659 -0
- package/dist/chunk-L2CBG5IX.js +1892 -0
- package/dist/chunk-L2S235CU.js +314 -0
- package/dist/chunk-L32WD57F.js +98 -0
- package/dist/chunk-LDJCKAJP.js +123 -0
- package/dist/chunk-LDWNBEN7.js +541 -0
- package/dist/chunk-LJVH57G2.js +241 -0
- package/dist/chunk-LPQ7NZEN.js +209 -0
- package/dist/chunk-LRGH3CCV.js +148 -0
- package/dist/chunk-LULOGMMM.js +1133 -0
- package/dist/chunk-LWGKN2GY.js +8284 -0
- package/dist/chunk-LX4F24ZH.js +157 -0
- package/dist/chunk-M4BUEXXF.js +839 -0
- package/dist/chunk-M6UHOC4I.js +553 -0
- package/dist/chunk-MCBNWZNO.js +657 -0
- package/dist/chunk-MEMSY6VY.js +223 -0
- package/dist/chunk-MHLEZQQI.js +145 -0
- package/dist/chunk-MJ6WNSIX.js +192 -0
- package/dist/chunk-MKASIKWZ.js +442 -0
- package/dist/chunk-MNOVBYFU.js +60 -0
- package/dist/chunk-MO2BYAAF.js +2896 -0
- package/dist/chunk-MP6P7Y2A.js +180 -0
- package/dist/chunk-MQ4TFJAG.js +3365 -0
- package/dist/chunk-MT2WAIR3.js +131 -0
- package/dist/chunk-MTU7VTNN.js +1122 -0
- package/dist/chunk-MULMXX4V.js +150 -0
- package/dist/chunk-MW2DWWVA.js +147 -0
- package/dist/chunk-MY3ZPUN4.js +151 -0
- package/dist/chunk-MYLH2S4G.js +64 -0
- package/dist/chunk-MZ5WYKNA.js +28 -0
- package/dist/chunk-N3Q2JNPV.js +249 -0
- package/dist/chunk-N5DX4JAK.js +12 -0
- package/dist/chunk-NAINGKHF.js +35 -0
- package/dist/chunk-NEQUQHOX.js +223 -0
- package/dist/chunk-NHNPEZS4.js +1408 -0
- package/dist/chunk-NHTIXJCR.js +17 -0
- package/dist/chunk-NKLPMBNV.js +1154 -0
- package/dist/chunk-NMVZ5YES.js +199 -0
- package/dist/chunk-NRFRY3GE.js +256 -0
- package/dist/chunk-NTD6CSI6.js +1266 -0
- package/dist/chunk-NUO7B3SH.js +318 -0
- package/dist/chunk-NVOHIJKG.js +1319 -0
- package/dist/chunk-NXH3RX7O.js +523 -0
- package/dist/chunk-NXZ3DURF.js +257 -0
- package/dist/chunk-O26L6F7F.js +209 -0
- package/dist/chunk-O5RRIUEP.js +124 -0
- package/dist/chunk-O5X6BKAO.js +10 -0
- package/dist/chunk-O6ZQUVTR.js +31 -0
- package/dist/chunk-O7FE75QZ.js +306 -0
- package/dist/chunk-OC7Z6AJQ.js +81 -0
- package/dist/chunk-OH7IEZ63.js +307 -0
- package/dist/chunk-OI734GRS.js +115 -0
- package/dist/chunk-OJBH62DK.js +684 -0
- package/dist/chunk-OKJLVSTJ.js +247 -0
- package/dist/chunk-OMX5VL43.js +79 -0
- package/dist/chunk-ONHAMBN6.js +12 -0
- package/dist/chunk-ONYPLIVE.js +753 -0
- package/dist/chunk-OSK7QWPD.js +637 -0
- package/dist/chunk-OTXLMJKP.js +438 -0
- package/dist/chunk-OV5PKQMA.js +213 -0
- package/dist/chunk-OXK5HA4B.js +31 -0
- package/dist/chunk-OYDWFQA4.js +272 -0
- package/dist/chunk-OZAUKWHB.js +22 -0
- package/dist/chunk-P5P2EKGH.js +94 -0
- package/dist/chunk-P6SSWAKY.js +29 -0
- package/dist/chunk-P7CQGPLS.js +30 -0
- package/dist/chunk-P7RVCHQV.js +197 -0
- package/dist/chunk-PEBWDYIW.js +726 -0
- package/dist/chunk-PFCXFO7A.js +9193 -0
- package/dist/chunk-PFLDWMX6.js +168 -0
- package/dist/chunk-PHZGLQSK.js +15883 -0
- package/dist/chunk-PNFTPNSR.js +20 -0
- package/dist/chunk-PPHXX4W7.js +175 -0
- package/dist/chunk-PPZESPJT.js +262 -0
- package/dist/chunk-PRDHI2L5.js +296 -0
- package/dist/chunk-PSTKWHHN.js +32 -0
- package/dist/chunk-PV53ODOT.js +555 -0
- package/dist/chunk-PVJBTI7P.js +24 -0
- package/dist/chunk-PXKERBKX.js +100 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-Q3DHEZA5.js +85 -0
- package/dist/chunk-Q5RZDVQK.js +204 -0
- package/dist/chunk-Q64IR2Z2.js +4933 -0
- package/dist/chunk-Q6Z5UGPL.js +398 -0
- package/dist/chunk-QAMAV4DT.js +627 -0
- package/dist/chunk-QBWF4444.js +112 -0
- package/dist/chunk-QDKYT4CI.js +22805 -0
- package/dist/chunk-QFLBRWCJ.js +126 -0
- package/dist/chunk-QILAQACE.js +107 -0
- package/dist/chunk-QJ6OEH4K.js +3683 -0
- package/dist/chunk-QK3NLYTV.js +320 -0
- package/dist/chunk-QOFLUS24.js +535 -0
- package/dist/chunk-QOIASNEJ.js +98 -0
- package/dist/chunk-QP4UTSOV.js +3587 -0
- package/dist/chunk-QP5ST7RI.js +904 -0
- package/dist/chunk-QS5AGRZN.js +167 -0
- package/dist/chunk-QTAOU53N.js +107 -0
- package/dist/chunk-QTCLJHLZ.js +351 -0
- package/dist/chunk-QTOLTG4L.js +119 -0
- package/dist/chunk-QWDV4OSL.js +142 -0
- package/dist/chunk-QWMQ74KD.js +508 -0
- package/dist/chunk-QX6LOMMH.js +158 -0
- package/dist/chunk-QXBKIHY7.js +238 -0
- package/dist/chunk-QXBKTJT6.js +270 -0
- package/dist/chunk-QYQOVOLR.js +196 -0
- package/dist/chunk-R2CKS6WT.js +73 -0
- package/dist/chunk-R2TIYG7U.js +68 -0
- package/dist/chunk-R2W3LESH.js +465 -0
- package/dist/chunk-R5EXHMUN.js +100 -0
- package/dist/chunk-RBBTBCHX.js +289 -0
- package/dist/chunk-RBKLOUXA.js +121 -0
- package/dist/chunk-RCP2Y6DQ.js +86 -0
- package/dist/chunk-RCYEJSD5.js +260 -0
- package/dist/chunk-RFDVSI4N.js +134 -0
- package/dist/chunk-RGJU63FV.js +1717 -0
- package/dist/chunk-RI4EX2QE.js +18 -0
- package/dist/chunk-RIMARPCF.js +152 -0
- package/dist/chunk-RJ6RSNT6.js +42 -0
- package/dist/chunk-RNI2GUIF.js +131 -0
- package/dist/chunk-RPHUWJ3K.js +264 -0
- package/dist/chunk-RPIMAMV7.js +361 -0
- package/dist/chunk-RQFX6YRN.js +49 -0
- package/dist/chunk-RRC5BL7B.js +285 -0
- package/dist/chunk-RSYTSBOO.js +79 -0
- package/dist/chunk-RWQQIKIC.js +35 -0
- package/dist/chunk-RZUBOZH2.js +373 -0
- package/dist/chunk-S2E632FQ.js +380 -0
- package/dist/chunk-S2VQCZO4.js +22 -0
- package/dist/chunk-S2XIMQJQ.js +681 -0
- package/dist/chunk-S47ISY27.js +183 -0
- package/dist/chunk-S6I4KISU.js +142 -0
- package/dist/chunk-S6TAFQI5.js +2403 -0
- package/dist/chunk-SBBNT5ZJ.js +27 -0
- package/dist/chunk-SDJV2DTQ.js +491 -0
- package/dist/chunk-SDPIFNLD.js +33 -0
- package/dist/chunk-SF6YK3JI.js +835 -0
- package/dist/chunk-SK3ZFONO.js +976 -0
- package/dist/chunk-SKTFRFQ7.js +1775 -0
- package/dist/chunk-SNGM452K.js +2002 -0
- package/dist/chunk-STW7OFOA.js +581 -0
- package/dist/chunk-SU6UFUQY.js +1351 -0
- package/dist/chunk-SVJM3LJA.js +27 -0
- package/dist/chunk-SVUNZOPG.js +129 -0
- package/dist/chunk-T5XLUYV3.js +34 -0
- package/dist/chunk-T7E5JS6N.js +57 -0
- package/dist/chunk-TA3N5MMO.js +450 -0
- package/dist/chunk-TAJPCXSB.js +13 -0
- package/dist/chunk-TDEMDRN2.js +1639 -0
- package/dist/chunk-TDSEX5CF.js +24 -0
- package/dist/chunk-TGCVL74Y.js +28 -0
- package/dist/chunk-TGXXYDL4.js +1012 -0
- package/dist/chunk-TIKTYUJO.js +296 -0
- package/dist/chunk-TIZAJD4M.js +6029 -0
- package/dist/chunk-TLSEZZCG.js +5549 -0
- package/dist/chunk-TMBDRHAX.js +19 -0
- package/dist/chunk-TRURQUBP.js +1217 -0
- package/dist/chunk-TSQ256N5.js +152 -0
- package/dist/chunk-TUQMMFEQ.js +2244 -0
- package/dist/chunk-U424MJLC.js +81 -0
- package/dist/chunk-U4DRS6ZW.js +11 -0
- package/dist/chunk-U5KS22IN.js +204 -0
- package/dist/chunk-UAPHFJPN.js +673 -0
- package/dist/chunk-UB7LQITD.js +1040 -0
- package/dist/chunk-UBVRHGAZ.js +237 -0
- package/dist/chunk-UDNNAQN5.js +98 -0
- package/dist/chunk-UJP3XM54.js +342 -0
- package/dist/chunk-UJYDH2MW.js +305 -0
- package/dist/chunk-UKMPASIC.js +399 -0
- package/dist/chunk-UKUL3EHK.js +763 -0
- package/dist/chunk-UPHU64NB.js +1327 -0
- package/dist/chunk-URMSZXAD.js +43 -0
- package/dist/chunk-US3RT6CY.js +525 -0
- package/dist/chunk-UTDEOPKV.js +30 -0
- package/dist/chunk-UUUDKV7N.js +13 -0
- package/dist/chunk-UVKWU3SM.js +39 -0
- package/dist/chunk-V2TVPO3X.js +521 -0
- package/dist/chunk-V3KLVK7B.js +398 -0
- package/dist/chunk-V3YFFHDR.js +17 -0
- package/dist/chunk-VAG6GUBR.js +1162 -0
- package/dist/chunk-VBENAHDA.js +1814 -0
- package/dist/chunk-VBNCKCCI.js +16 -0
- package/dist/chunk-VCGSWN5M.js +1300 -0
- package/dist/chunk-VE2O2I7M.js +115 -0
- package/dist/chunk-VIFHYTI5.js +31 -0
- package/dist/chunk-VJER3FUS.js +5080 -0
- package/dist/chunk-VLMDM56Q.js +268 -0
- package/dist/chunk-VMFZEVVX.js +99 -0
- package/dist/chunk-VNDPQVUZ.js +1853 -0
- package/dist/chunk-VSPJRLGA.js +4783 -0
- package/dist/chunk-VSWC5ZXI.js +103 -0
- package/dist/chunk-VYI4DSZP.js +2017 -0
- package/dist/chunk-W4BAIQ6L.js +1212 -0
- package/dist/chunk-W7VQAL2K.js +2203 -0
- package/dist/chunk-WEI3ZDZH.js +33 -0
- package/dist/chunk-WEIUS5NT.js +26 -0
- package/dist/chunk-WEW7UFWE.js +546 -0
- package/dist/chunk-WGQAXLNA.js +384 -0
- package/dist/chunk-WJAL4JL3.js +206 -0
- package/dist/chunk-WKFOJ5ZG.js +141 -0
- package/dist/chunk-WMWQEFLN.js +2177 -0
- package/dist/chunk-WNECKKO2.js +154 -0
- package/dist/chunk-WPKLYOAM.js +195 -0
- package/dist/chunk-WSH37YQN.js +243 -0
- package/dist/chunk-WV3KQDPE.js +84 -0
- package/dist/chunk-WZFC4V4U.js +389 -0
- package/dist/chunk-XCOIBNET.js +13 -0
- package/dist/chunk-XDM65LBX.js +53 -0
- package/dist/chunk-XGU6PBO5.js +645 -0
- package/dist/chunk-XHOI4V62.js +43 -0
- package/dist/chunk-XIBVFMLQ.js +6858 -0
- package/dist/chunk-XJ2E7YEJ.js +117 -0
- package/dist/chunk-XON4ZCYX.js +69 -0
- package/dist/chunk-XQPEWJPI.js +127 -0
- package/dist/chunk-XSJTIFUY.js +31 -0
- package/dist/chunk-XT7OVWM3.js +64 -0
- package/dist/chunk-XTXYIISX.js +1104 -0
- package/dist/chunk-XV7NZL4B.js +57 -0
- package/dist/chunk-XWIZ6B5S.js +109 -0
- package/dist/chunk-XWLIKOBX.js +240 -0
- package/dist/chunk-XZOXEIN4.js +156 -0
- package/dist/chunk-XZRS7I7W.js +3673 -0
- package/dist/chunk-Y4ZDDHB6.js +44 -0
- package/dist/chunk-Y733RB7T.js +194 -0
- package/dist/chunk-YBHUB4XR.js +14931 -0
- package/dist/chunk-YKC75XK4.js +147 -0
- package/dist/chunk-YKMXFVK7.js +27 -0
- package/dist/chunk-YO5BAJ55.js +225 -0
- package/dist/chunk-YPDCLXNZ.js +568 -0
- package/dist/chunk-YSQPGVFK.js +116 -0
- package/dist/chunk-YTVVTIWJ.js +716 -0
- package/dist/chunk-YVCMDVBB.js +698 -0
- package/dist/chunk-YYTOA6DP.js +114 -0
- package/dist/chunk-Z5WGNULV.js +88 -0
- package/dist/chunk-Z6NGVVXG.js +66 -0
- package/dist/chunk-Z7CDQNH3.js +20 -0
- package/dist/chunk-ZH4G3FU5.js +187 -0
- package/dist/chunk-ZJB6SQPA.js +257 -0
- package/dist/chunk-ZL6E4HMY.js +42 -0
- package/dist/chunk-ZMLOC3DW.js +62 -0
- package/dist/chunk-ZORW64QH.js +101 -0
- package/dist/chunk-ZVVD2XGV.js +1369 -0
- package/dist/cli-utils-ER33ODC5.js +31 -0
- package/dist/cli-validation-6CHPTANV.js +146 -0
- package/dist/commit-utils-3STAY7C7.js +49 -0
- package/dist/compatibility-notice-IHNXR2JR.js +21 -0
- package/dist/compete-config-resolver-V3GDNJMM.js +125 -0
- package/dist/compete-results-XUM3PJHS.js +318 -0
- package/dist/competition-outcome-tracker-NWSMVUPG.js +62 -0
- package/dist/config-4HHE4OIK.js +238 -0
- package/dist/config-D6YAZASH.js +10 -0
- package/dist/config-MIPRDYYV.js +46 -0
- package/dist/config-audit-GA5DCUAI.js +371 -0
- package/dist/config-explain-4LMO556H.js +71 -0
- package/dist/config-loader-RGP634PC.js +51 -0
- package/dist/config-manager-J2UTO635.js +114 -0
- package/dist/config-merger-TVUJBZTS.js +14 -0
- package/dist/config-validator-H4ZVIE56.js +122 -0
- package/dist/cost-HSIV5U2Y.js +85 -0
- package/dist/data-migration-state-OAUL2WPQ.js +35 -0
- package/dist/db-migrator-NUOW5NKR.js +34 -0
- package/dist/default-paths-UILB62BN.js +49 -0
- package/dist/discovery-V6GYUJUC.js +53 -0
- package/dist/doctor-BBSMCDWE.js +271 -0
- package/dist/doctor-context-blocks-LPY75VZA.js +231 -0
- package/dist/doctor-instructions-import-JXHDDOE5.js +70 -0
- package/dist/doctor-purpose-6YJOXPBE.js +30 -0
- package/dist/domain-analyzer-S35ABUNA.js +48 -0
- package/dist/ensure-initialized-Q6PEU55O.js +78 -0
- package/dist/entry.js +922 -0
- package/dist/error-parser-core-MZFMFANN.js +70 -0
- package/dist/evaluation-context-GQBYDAEV.js +63 -0
- package/dist/evidence-plan-resolver-5FUAP3SA.js +111 -0
- package/dist/execution-data-writer-YCG5UH37.js +72 -0
- package/dist/execution-policy-applier-PWZM5LNA.js +63 -0
- package/dist/execution-preflight-DASJWWQF.js +223 -0
- package/dist/execution-roles-KNSNNB2D.js +112 -0
- package/dist/exit-code-error-QIY5DOKF.js +18 -0
- package/dist/external-temp-cleanup-CAW7LQ6R.js +360 -0
- package/dist/factory-YCYCB7AE.js +209 -0
- package/dist/file-utils-ZQYNBQBW.js +45 -0
- package/dist/flywheel-allowlist-revert-H22HA6JE.js +21 -0
- package/dist/flywheel-autoloop-executor-PDOASJEZ.js +316 -0
- package/dist/flywheel-autoloop-unverified-TOKYFCDC.js +36 -0
- package/dist/flywheel-competition-executor-YJUMMEJO.js +320 -0
- package/dist/flywheel-executor-JWU43KAY.js +437 -0
- package/dist/flywheel-git-isolation-FQ2TRWMB.js +56 -0
- package/dist/flywheel-manifest-HDPNMWWF.js +114 -0
- package/dist/flywheel-model-defaults-UHAKTZT3.js +110 -0
- package/dist/flywheel-preflight-III2C7TH.js +163 -0
- package/dist/flywheel-resume-2O45MS7Y.js +31 -0
- package/dist/flywheel-resume-execution-lease-P2BK257J.js +59 -0
- package/dist/flywheel-safety-AVAVFXIJ.js +19 -0
- package/dist/flywheel-scope-decision-ALJUUNGG.js +14 -0
- package/dist/get-telemetry-logger-MPDVO2JA.js +155 -0
- package/dist/git-message-style-KCXGMTDT.js +104 -0
- package/dist/guidance-profile-config-G3K5YQOO.js +113 -0
- package/dist/guidance-profile-renderer-TX4TNDFL.js +23 -0
- package/dist/identity-XLQJ4YJF.js +25 -0
- package/dist/index-query-6NUY4FKX.js +37 -0
- package/dist/index.js +85588 -0
- package/dist/indexer-DL54CM3K.js +44 -0
- package/dist/init-OUZ72344.js +245 -0
- package/dist/instructions-import-offer-NSYXSITC.js +363 -0
- package/dist/instructions-run-NBII7W36.js +114 -0
- package/dist/interrupted-work-E4TLKJLE.js +43 -0
- package/dist/invoke-provider-auth-NEWEIHCI.js +38 -0
- package/dist/judge-scoring.json +64 -0
- package/dist/lab-mode-YXQ2Y3NG.js +44 -0
- package/dist/lab-utils-5QAMDJR5.js +29 -0
- package/dist/linux-host-class-YDWQX65O.js +31 -0
- package/dist/llm-judge-executor-OJSNCTFJ.js +179 -0
- package/dist/local-ci-config-VZ4WWDT5.js +48 -0
- package/dist/local-executor-DCMDDRX2.js +280 -0
- package/dist/logger-5KNKS3O4.js +154 -0
- package/dist/matrix-prompt-builder-4ZBOGZSB.js +527 -0
- package/dist/matrix-result-parser-EZJM4YXP.js +14 -0
- package/dist/merge-batch-RTEEMVDB.js +313 -0
- package/dist/merge-batch-members-VIWGVRHP.js +24 -0
- package/dist/merge-queue-TIIUSOY6.js +126 -0
- package/dist/merge-queue-trust-DUNZ4J65.js +21 -0
- package/dist/metadata-UKSMWM6J.js +29 -0
- package/dist/model-catalog-readiness-R5KJBP5M.js +202 -0
- package/dist/model-invocations-db-XTUPPQ34.js +70 -0
- package/dist/model-resolver-JFZ2MHOJ.js +39 -0
- package/dist/model-resolver-SCQ57KAQ.js +217 -0
- package/dist/model-roles-D3OR4NAH.js +110 -0
- package/dist/monitor-F4SIU5G2.js +96 -0
- package/dist/nested-worktrees-2WUWQ75N.js +23 -0
- package/dist/next-S3IRLVER.js +96 -0
- package/dist/next-UKPFRXOO.js +148 -0
- package/dist/opportunistic-TL4S26VX.js +86 -0
- package/dist/optimization-config-resolver-NAEIYU6A.js +148 -0
- package/dist/package-HJR4QJT4.js +10 -0
- package/dist/parse-git-status-7PIYFPS2.js +21 -0
- package/dist/path-security-5WJSY56G.js +41 -0
- package/dist/paths-JUI7C4KU.js +45 -0
- package/dist/plan-backlog-list-fast-SSTQL5XP.js +89 -0
- package/dist/plan-backlog-show-fast-PFRAQ3JB.js +114 -0
- package/dist/plan-sprint-list-fast-HD2KBZSY.js +110 -0
- package/dist/plan-sprint-status-fast-YFYH7WQF.js +116 -0
- package/dist/plan-task-status-fast-JU5PNLUU.js +114 -0
- package/dist/plan-to-flywheel-PY5MSY2H.js +529 -0
- package/dist/preflight-PJHGTJNZ.js +12 -0
- package/dist/process-registry-B4ZUCN7T.js +28 -0
- package/dist/process-scanner-TU3QHWVB.js +23 -0
- package/dist/processes-6FHVGT72.js +28 -0
- package/dist/processes-json-fast-YWTMUWXB.js +72 -0
- package/dist/profile-HWTC56SG.js +209 -0
- package/dist/project-runtime-paths-2UQNG5Y4.js +41 -0
- package/dist/protected-branches-N36LDBFY.js +71 -0
- package/dist/provider-aware-resource-manager-YTFAHNGJ.js +40 -0
- package/dist/provider-matrix-2KO2V5OM.js +214 -0
- package/dist/provider-matrix-renderer-MEC3PER7.js +139 -0
- package/dist/provider-output-forwarding-2AHL7YMA.js +26 -0
- package/dist/provider-registry-WCZ65QHC.js +136 -0
- package/dist/provider-temp-cleanup-IUNPJCZF.js +14 -0
- package/dist/prune-engine-EOQVWEP6.js +448 -0
- package/dist/publication-turn-S42RZTQF.js +44 -0
- package/dist/quality-gate-JQMVCKOO.js +72 -0
- package/dist/quickstart-3O6FRCFB.js +105 -0
- package/dist/readiness-GW6ABRHB.js +139 -0
- package/dist/readiness-KFZCXDTT.js +215 -0
- package/dist/registry-7XG376SY.js +217 -0
- package/dist/repair-helpers-TTV5FEF4.js +117 -0
- package/dist/repo-root-YG6MQZDW.js +28 -0
- package/dist/resolution-engine-FFEK7HQ2.js +187 -0
- package/dist/resolve-provider-cli-ZEXVZKET.js +44 -0
- package/dist/restore-telemetry-db-E7BR5PO7.js +459 -0
- package/dist/routing-events-AW6SRZ6G.js +13 -0
- package/dist/routing-history-store-FMDDQL6W.js +39 -0
- package/dist/routing-planner-C6OHO3EY.js +221 -0
- package/dist/runner-KHDNQ5KU.js +1194 -0
- package/dist/runner-PIS2BW7Y.js +1801 -0
- package/dist/safety-E3JJVSFL.js +67 -0
- package/dist/safety-stanza-HE6GXP4G.js +49 -0
- package/dist/sandbox-7LBZC5PE.js +72 -0
- package/dist/sandbox-ASWCDYZL.js +80 -0
- package/dist/schema-extensions.sql +234 -0
- package/dist/security-JI22TBEB.js +17 -0
- package/dist/setup-bb-plugin-FQXLA76Y.js +531 -0
- package/dist/setup-claude-code-NF4ZSHVE.js +197 -0
- package/dist/setup-temp-cleanup-2APZ4HMA.js +29 -0
- package/dist/shared-utils-34HJZ6JX.js +40 -0
- package/dist/simple-artifact-goal-MQLDDMMX.js +29 -0
- package/dist/sprint-N7JFTEGL.js +403 -0
- package/dist/sprint-bridge-KJXIBNRJ.js +117 -0
- package/dist/sprint-completion-dependents-34SR7JVL.js +43 -0
- package/dist/sprint-execution-service-A6JPUBWY.js +376 -0
- package/dist/sprint-manager-MU2ZUWN2.js +140 -0
- package/dist/sqlite-timeout-config-U2CT4LKU.js +13 -0
- package/dist/sqlite-wrapper-FSGJ2QEW.js +19 -0
- package/dist/stale-cleanup-orchestrator-4X6324VF.js +71 -0
- package/dist/storage-access-I3V5LER7.js +31 -0
- package/dist/store-XVG64ZR2.js +25 -0
- package/dist/task-classifier-MN2DYE5R.js +558 -0
- package/dist/task-list-fast-7AT6LTTA.js +112 -0
- package/dist/task-manager-LHPBBRRE.js +136 -0
- package/dist/task-report-OLPAWBTK.js +64 -0
- package/dist/task-type-detector-6RG3YNZF.js +27 -0
- package/dist/task-type-resolver-EQHOUIHT.js +64 -0
- package/dist/telemetry-5VYSK772.js +212 -0
- package/dist/telemetry-health-tracker-4GFW2EMP.js +12 -0
- package/dist/telemetry-path-resolver-H3NFHHQY.js +34 -0
- package/dist/telemetry-reliability-URJDTNPZ.js +76 -0
- package/dist/transports-NREZQTJK.js +106 -0
- package/dist/unified-cost-tracker-5MTBJ2CL.js +59 -0
- package/dist/unified-state-cleanup-GGAXLHXF.js +292 -0
- package/dist/universal-cost-calculator-JTFEJVWK.js +70 -0
- package/dist/user-agents-QLIGPLP2.js +30 -0
- package/dist/variant-execution-I4ZZUAWY.js +319 -0
- package/dist/variant-opt-parser-JHCXKY2K.js +251 -0
- package/dist/variant-request-input-PL37N3AN.js +11 -0
- package/dist/variant-state-cleanup-KHMXRBZN.js +287 -0
- package/dist/variant-worker-COB5YTJX.js +4517 -0
- package/dist/variant-worker.js +26 -0
- package/dist/variants-XGDSXYG3.js +267 -0
- package/dist/verification-command-YSPU4VBH.js +66 -0
- package/dist/verify-command-timeout-DQUYH33Z.js +11 -0
- package/dist/verify-commands-LOHMOFKC.js +65 -0
- package/dist/warning-JSKSFKBT.js +13 -0
- package/dist/work-edge-K65NHVWY.js +50 -0
- package/dist/work-item-writer-KY5ZWZWW.js +30 -0
- package/dist/worker-JNXXEIT6.js +1504 -0
- package/dist/worker.js +21 -0
- package/dist/workspace-tree-identity-JO5PSKTW.js +52 -0
- package/dist/worktree-environment-WTVABAQW.js +64 -0
- package/dist/worktree-metrics-VZRQI26T.js +66 -0
- package/dist/writeback-TQFQBJNU.js +50 -0
- package/docs/CLI_REFERENCE.md +8287 -0
- package/docs/PROVIDER_SETUP.md +1737 -0
- package/docs/README.md +96 -0
- package/docs/TROUBLESHOOTING.md +2209 -0
- package/docs/getting-started/QUICK_START.md +260 -0
- package/docs/getting-started/README.md +198 -0
- package/docs/getting-started/SETUP.md +25 -0
- package/docs/reference/AGENT_CONTEXT.md +84 -0
- package/docs/reference/DELIVERY_ONBOARDING.md +198 -0
- package/docs/reference/PROVIDER_RELEASE_TIERS.md +34 -0
- package/docs/reference/SECURITY.md +291 -0
- package/docs/reference/TELEMETRY_PRIVACY.md +259 -0
- package/integrations/bb-plugin-build/README.md +391 -0
- package/integrations/bb-plugin-build/app.css +431 -0
- package/integrations/bb-plugin-build/app.tsx +6210 -0
- package/integrations/bb-plugin-build/package.json +45 -0
- package/integrations/bb-plugin-build/server.ts +488 -0
- package/integrations/bb-plugin-build/src/adapter.ts +3672 -0
- package/integrations/bb-plugin-build/src/backlog-authoring-model.ts +150 -0
- package/integrations/bb-plugin-build/src/backlog-authoring-schema.ts +458 -0
- package/integrations/bb-plugin-build/src/backlog-authoring-service.ts +467 -0
- package/integrations/bb-plugin-build/src/backlog-authoring-view.ts +1025 -0
- package/integrations/bb-plugin-build/src/command-evidence-schema.ts +13 -0
- package/integrations/bb-plugin-build/src/competition-defaults-schema.ts +122 -0
- package/integrations/bb-plugin-build/src/competition-defaults-service.ts +105 -0
- package/integrations/bb-plugin-build/src/competition-defaults-view.ts +138 -0
- package/integrations/bb-plugin-build/src/contract.ts +718 -0
- package/integrations/bb-plugin-build/src/execution-activity-view.ts +98 -0
- package/integrations/bb-plugin-build/src/finalize-model.ts +205 -0
- package/integrations/bb-plugin-build/src/finalize-schema.ts +150 -0
- package/integrations/bb-plugin-build/src/finalize-service.ts +330 -0
- package/integrations/bb-plugin-build/src/finalize-view.ts +159 -0
- package/integrations/bb-plugin-build/src/judge-operations-readback.ts +303 -0
- package/integrations/bb-plugin-build/src/judge-operations-schema.ts +33 -0
- package/integrations/bb-plugin-build/src/judge-operations-view.ts +459 -0
- package/integrations/bb-plugin-build/src/model.ts +1318 -0
- package/integrations/bb-plugin-build/src/monitor-service.ts +233 -0
- package/integrations/bb-plugin-build/src/panel-read-ux.ts +97 -0
- package/integrations/bb-plugin-build/src/patch-preview-schema.ts +166 -0
- package/integrations/bb-plugin-build/src/patch-preview-service.ts +480 -0
- package/integrations/bb-plugin-build/src/patch-preview-view.ts +109 -0
- package/integrations/bb-plugin-build/src/planning-readback.ts +228 -0
- package/integrations/bb-plugin-build/src/planning-service.ts +647 -0
- package/integrations/bb-plugin-build/src/planning-sync-evidence.ts +37 -0
- package/integrations/bb-plugin-build/src/planning-workspace-readback.ts +33 -0
- package/integrations/bb-plugin-build/src/planning-workspace-schema.ts +131 -0
- package/integrations/bb-plugin-build/src/planning-workspace.ts +333 -0
- package/integrations/bb-plugin-build/src/poll-handoff.ts +136 -0
- package/integrations/bb-plugin-build/src/project-target-server.ts +21 -0
- package/integrations/bb-plugin-build/src/project-target.ts +45 -0
- package/integrations/bb-plugin-build/src/reference-index.ts +218 -0
- package/integrations/bb-plugin-build/src/repository-identity.ts +17 -0
- package/integrations/bb-plugin-build/src/repository-schema.ts +30 -0
- package/integrations/bb-plugin-build/src/result-explorer-view.ts +597 -0
- package/integrations/bb-plugin-build/src/result-readback-model.ts +163 -0
- package/integrations/bb-plugin-build/src/result-readback-schema.ts +319 -0
- package/integrations/bb-plugin-build/src/result-readback-service.ts +333 -0
- package/integrations/bb-plugin-build/src/run-page-view.ts +332 -0
- package/integrations/bb-plugin-build/src/setup-config-schema.ts +293 -0
- package/integrations/bb-plugin-build/src/setup-config-service.ts +620 -0
- package/integrations/bb-plugin-build/src/setup-config-view.ts +388 -0
- package/integrations/bb-plugin-build/src/sprint-close-model.ts +150 -0
- package/integrations/bb-plugin-build/src/sprint-close-schema.ts +128 -0
- package/integrations/bb-plugin-build/src/sprint-close-service.ts +293 -0
- package/integrations/bb-plugin-build/src/sprint-close-view.ts +116 -0
- package/integrations/bb-plugin-build/src/sprint-composition-model.ts +709 -0
- package/integrations/bb-plugin-build/src/sprint-composition-readback.ts +231 -0
- package/integrations/bb-plugin-build/src/sprint-composition-schema.ts +597 -0
- package/integrations/bb-plugin-build/src/sprint-composition-service.ts +773 -0
- package/integrations/bb-plugin-build/src/sprint-composition-view.ts +1221 -0
- package/integrations/bb-plugin-build/src/sprint-execution-schema.ts +188 -0
- package/integrations/bb-plugin-build/src/sprint-execution-service.ts +289 -0
- package/integrations/bb-plugin-build/src/sprint-execution-view.ts +54 -0
- package/integrations/bb-plugin-build/src/task-authoring-model.ts +563 -0
- package/integrations/bb-plugin-build/src/task-authoring-schema.ts +629 -0
- package/integrations/bb-plugin-build/src/task-authoring-service.ts +842 -0
- package/integrations/bb-plugin-build/src/task-authoring-view.ts +1227 -0
- package/integrations/bb-plugin-build/src/task-catalog-schema.ts +46 -0
- package/integrations/bb-plugin-build/src/task-catalog-service.ts +44 -0
- package/integrations/bb-plugin-build/src/task-detail-readback.ts +56 -0
- package/integrations/bb-plugin-build/src/task-detail-schema.ts +171 -0
- package/integrations/bb-plugin-build/src/task-navigation.ts +446 -0
- package/integrations/bb-plugin-build/src/workbench-route.ts +74 -0
- package/package.json +306 -4
- package/schemas/README.md +80 -0
- package/schemas/build.config.schema.json +2110 -0
- package/schemas/config-v1.schema.json +136 -0
- package/schemas/execution-config.schema.json +47 -0
- package/schemas/judge-scoring.schema.json +257 -0
- package/schemas/provider-config.schema.json +179 -0
- package/schemas/telemetry-config.schema.json +79 -0
- package/schemas/user-preferences.schema.json +141 -0
- package/scripts/assert-node-runtime.mjs +146 -0
- package/scripts/preflight-native.mjs +49 -0
- package/scripts/setup-git-hooks.mjs +39 -0
- package/scripts/sync.sh +2871 -0
- package/scripts/write-node-launcher.sh +138 -0
- package/src/resources/gemini/slash-packs/default/plan.toml +15 -0
- package/src/resources/gemini/slash-packs/default/summary.toml +16 -0
- package/src/resources/gemini/slash-packs/default/tests.toml +16 -0
- package/templates/.build-project/README.md +41 -0
- package/templates/.build-project/agents/README.md +50 -0
- package/templates/.build-project/agents/architect.md +10 -0
- package/templates/.build-project/agents/code-reviewer.md +10 -0
- package/templates/.build-project/agents/debugger.md +10 -0
- package/templates/.build-project/agents/documenter.md +10 -0
- package/templates/.build-project/agents/test-automator.md +10 -0
- package/templates/.build-project/config/build.config.jsonc +64 -0
- package/templates/.build-project/config/project-context.template.json +6 -0
- package/templates/.build-project/config/task-type-aliases.presets/kanban.yaml +14 -0
- package/templates/.build-project/config/task-type-aliases.presets/scrum.yaml +17 -0
- package/templates/.build-project/config/task-type-aliases.presets/xp.yaml +12 -0
- package/templates/.build-project/config/task-type-aliases.yaml +29 -0
- package/templates/.build-project/gitignore.template +70 -0
- package/templates/.build-project/task-types/analysis.yaml +38 -0
- package/templates/.build-project/task-types/architecture.yaml +35 -0
- package/templates/.build-project/task-types/doc.yaml +34 -0
- package/templates/.build-project/task-types/feature.yaml +22 -0
- package/templates/.build-project/task-types/general.yaml +4 -0
- package/templates/.build-project/task-types/security.yaml +38 -0
- package/templates/AGENTS.template.md +85 -0
- package/templates/CLAUDE.template.md +1 -0
- package/templates/GEMINI.template.md +1 -0
- package/templates/builtin-workflows/code-review.yaml +73 -0
- package/templates/builtin-workflows/skills/code-review/SKILL.md +61 -0
- package/templates/builtin-workflows/skills/execution-contract-analysis/SKILL.md +24 -0
- package/templates/builtin-workflows/skills/execution-contract-code/SKILL.md +19 -0
- package/templates/builtin-workflows/skills/execution-contract-structured/SKILL.md +17 -0
- package/templates/ci/incident-intake.mjs +787 -0
- package/templates/ci/incidents.yml.template +103 -0
- package/templates/ci/node.yml.template +17 -0
- package/templates/ci/python.yml.template +17 -0
- package/templates/guard-kit/GUARD_SETUP.md.template +258 -0
- package/templates/guard-kit/build-guard.yml.template +189 -0
- package/templates/guard-kit/check.mjs.template +932 -0
- package/templates/guard-kit/config.json.template +7 -0
- package/templates/guard-kit/policy.json.template +1 -0
- package/templates/profiles/README.md +57 -0
- package/templates/profiles/frontier-claude.json +23 -0
- package/templates/profiles/frontier-codex.json +23 -0
|
@@ -0,0 +1,4933 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ModelMetadataResolver
|
|
3
|
+
} from "./chunk-YVCMDVBB.js";
|
|
4
|
+
import {
|
|
5
|
+
CURSOR_KNOWN_MODELS
|
|
6
|
+
} from "./chunk-7HPCIOMA.js";
|
|
7
|
+
import {
|
|
8
|
+
parseJsonWithContext
|
|
9
|
+
} from "./chunk-P6SSWAKY.js";
|
|
10
|
+
import {
|
|
11
|
+
logWarning
|
|
12
|
+
} from "./chunk-ETAD4NKD.js";
|
|
13
|
+
import {
|
|
14
|
+
sqlite_wrapper_default
|
|
15
|
+
} from "./chunk-XQPEWJPI.js";
|
|
16
|
+
import {
|
|
17
|
+
wrapWithSandbox
|
|
18
|
+
} from "./chunk-B7YRSULE.js";
|
|
19
|
+
import {
|
|
20
|
+
TelemetryPathResolver
|
|
21
|
+
} from "./chunk-C3KCMAJP.js";
|
|
22
|
+
import {
|
|
23
|
+
applySqlitePragmas
|
|
24
|
+
} from "./chunk-HEP3ILP4.js";
|
|
25
|
+
import {
|
|
26
|
+
ProviderRegistry,
|
|
27
|
+
refreshInventoryContext,
|
|
28
|
+
registerProviderRegistryReloadInvalidator,
|
|
29
|
+
resolveSharedRefreshPaths
|
|
30
|
+
} from "./chunk-QJ6OEH4K.js";
|
|
31
|
+
import {
|
|
32
|
+
listModelRouteIds
|
|
33
|
+
} from "./chunk-QXBKTJT6.js";
|
|
34
|
+
import {
|
|
35
|
+
createScopedProviderChildEnv
|
|
36
|
+
} from "./chunk-WGQAXLNA.js";
|
|
37
|
+
import {
|
|
38
|
+
getBuildProjectRoot
|
|
39
|
+
} from "./chunk-CHA2HDE5.js";
|
|
40
|
+
import {
|
|
41
|
+
debugLog,
|
|
42
|
+
emitWarning
|
|
43
|
+
} from "./chunk-7VRJHTGZ.js";
|
|
44
|
+
|
|
45
|
+
// src/telemetry/model-invocations-db.ts
|
|
46
|
+
import { randomBytes } from "crypto";
|
|
47
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
48
|
+
import path5 from "path";
|
|
49
|
+
|
|
50
|
+
// src/telemetry/cost-precedence.ts
|
|
51
|
+
var NON_METERED_COST_SQL = `(
|
|
52
|
+
e.computed_cost_cents IS NULL
|
|
53
|
+
AND (
|
|
54
|
+
(e.billing_mode IS NOT NULL AND e.billing_mode <> 'metered')
|
|
55
|
+
OR (
|
|
56
|
+
e.cost_snapshot IS NOT NULL
|
|
57
|
+
AND json_valid(e.cost_snapshot)
|
|
58
|
+
AND json_extract(e.cost_snapshot, '$.billingMode') IS NOT NULL
|
|
59
|
+
AND json_extract(e.cost_snapshot, '$.billingMode') <> 'metered'
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
)`;
|
|
63
|
+
var SNAPSHOT_FIRST_COST_CENTS_SQL = `
|
|
64
|
+
CASE
|
|
65
|
+
WHEN ${NON_METERED_COST_SQL}
|
|
66
|
+
THEN NULL
|
|
67
|
+
WHEN e.cost_snapshot IS NOT NULL AND json_valid(e.cost_snapshot) THEN
|
|
68
|
+
COALESCE(
|
|
69
|
+
-- REAL (not INTEGER): snapshot costs carry sub-cent precision; an
|
|
70
|
+
-- integer cast truncates small invocations (< 1 cent) to zero.
|
|
71
|
+
CAST(json_extract(e.cost_snapshot, '$.totalCostCents') AS REAL),
|
|
72
|
+
e.computed_cost_cents
|
|
73
|
+
)
|
|
74
|
+
ELSE e.computed_cost_cents
|
|
75
|
+
END
|
|
76
|
+
`;
|
|
77
|
+
function resolveSnapshotFirstCostCents(input) {
|
|
78
|
+
const billingMode = input.costSnapshot?.billingMode;
|
|
79
|
+
if (input.computedCostCents == null && (typeof billingMode === "string" && billingMode !== "metered" || typeof input.billingMode === "string" && input.billingMode !== "metered")) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const snapshotCost = input.costSnapshot?.totalCostCents;
|
|
83
|
+
if (typeof snapshotCost === "number" && Number.isFinite(snapshotCost)) {
|
|
84
|
+
return snapshotCost;
|
|
85
|
+
}
|
|
86
|
+
const computedCost = input.computedCostCents;
|
|
87
|
+
return typeof computedCost === "number" && Number.isFinite(computedCost) ? computedCost : null;
|
|
88
|
+
}
|
|
89
|
+
function finiteNumberOrZero(value) {
|
|
90
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
91
|
+
}
|
|
92
|
+
function resolveSnapshotFirstCostSnapshot(input) {
|
|
93
|
+
const totalCostCents = resolveSnapshotFirstCostCents(input);
|
|
94
|
+
if (totalCostCents === null) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const snapshot = input.costSnapshot;
|
|
98
|
+
return {
|
|
99
|
+
inputCostCents: finiteNumberOrZero(snapshot?.inputCostCents),
|
|
100
|
+
outputCostCents: finiteNumberOrZero(snapshot?.outputCostCents),
|
|
101
|
+
cacheCostCents: finiteNumberOrZero(snapshot?.cacheCostCents),
|
|
102
|
+
toolCostCents: finiteNumberOrZero(snapshot?.toolCostCents),
|
|
103
|
+
infrastructureCostCents: finiteNumberOrZero(snapshot?.infrastructureCostCents),
|
|
104
|
+
totalCostCents,
|
|
105
|
+
currency: typeof snapshot?.currency === "string" && snapshot.currency ? snapshot.currency : "USD"
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/telemetry/invocation-cost-enrichment.ts
|
|
110
|
+
import fs3 from "fs";
|
|
111
|
+
import path4 from "path";
|
|
112
|
+
|
|
113
|
+
// src/core/config/model-registry.ts
|
|
114
|
+
import * as crypto from "crypto";
|
|
115
|
+
import * as fs from "fs";
|
|
116
|
+
import * as path from "path";
|
|
117
|
+
import yaml from "js-yaml";
|
|
118
|
+
import { z } from "zod";
|
|
119
|
+
var BillingModeSchema = z.enum(["metered", "flat_rate", "non_metered"]);
|
|
120
|
+
var CostSchemaZ = z.object({
|
|
121
|
+
prompt_per_1k: z.number().min(0).optional(),
|
|
122
|
+
completion_per_1k: z.number().min(0).optional(),
|
|
123
|
+
cache_read_per_1k: z.number().min(0).optional(),
|
|
124
|
+
cache_write_per_1k: z.number().min(0).optional(),
|
|
125
|
+
/** Cache writes held for the longer duration (Anthropic's 1-hour write); absent prices them at `cache_write_per_1k`. */
|
|
126
|
+
cache_write_1h_per_1k: z.number().min(0).optional(),
|
|
127
|
+
reasoning_per_1k: z.number().min(0).optional(),
|
|
128
|
+
tool_per_1k: z.number().min(0).optional(),
|
|
129
|
+
currency: z.string().min(1).optional(),
|
|
130
|
+
long_context_pricing: z.record(z.string(), z.unknown()).optional(),
|
|
131
|
+
/** Per speed-tier rate sets in the same per-1k shape as the standard rates, keyed by tier name. */
|
|
132
|
+
speed_tiers: z.record(z.string(), z.record(z.string(), z.unknown())).optional(),
|
|
133
|
+
/** Tier a model id selected (e.g. a `-fast` id) and the rate set priced; `applied` is `standard` when the tier had no rates. */
|
|
134
|
+
routed_speed_tier: z.object({
|
|
135
|
+
requested: z.string(),
|
|
136
|
+
applied: z.string(),
|
|
137
|
+
unpublishedLongContextAboveTokens: z.number().optional()
|
|
138
|
+
}).optional(),
|
|
139
|
+
extra_fees: z.record(z.string(), z.number()).optional(),
|
|
140
|
+
billing_mode: BillingModeSchema.optional(),
|
|
141
|
+
billing_source: z.string().optional(),
|
|
142
|
+
pricing_source: z.string().optional(),
|
|
143
|
+
/** How the rates were reached when not from the model's own row, e.g. `passthrough:<provider>/<model>`. */
|
|
144
|
+
resolution_route: z.string().optional(),
|
|
145
|
+
pricing_confidence: z.enum(["high", "medium", "low"]).optional(),
|
|
146
|
+
flat_rate_per_seat_month: z.number().min(0).optional(),
|
|
147
|
+
notes: z.string().optional()
|
|
148
|
+
}).superRefine((value, ctx) => {
|
|
149
|
+
const billingMode = value.billing_mode ?? "metered";
|
|
150
|
+
if (billingMode === "metered") {
|
|
151
|
+
if (typeof value.prompt_per_1k !== "number") {
|
|
152
|
+
ctx.addIssue({
|
|
153
|
+
code: z.ZodIssueCode.custom,
|
|
154
|
+
message: "Metered billing requires prompt_per_1k",
|
|
155
|
+
path: ["prompt_per_1k"]
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (typeof value.completion_per_1k !== "number") {
|
|
159
|
+
ctx.addIssue({
|
|
160
|
+
code: z.ZodIssueCode.custom,
|
|
161
|
+
message: "Metered billing requires completion_per_1k",
|
|
162
|
+
path: ["completion_per_1k"]
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
var ModelDefZ = z.object({
|
|
168
|
+
id: z.string().min(1),
|
|
169
|
+
display_name: z.string().min(1),
|
|
170
|
+
capabilities: z.array(z.string()),
|
|
171
|
+
costSchema: CostSchemaZ.optional()
|
|
172
|
+
});
|
|
173
|
+
var ProviderFrontmatterZ = z.object({
|
|
174
|
+
id: z.string().min(1),
|
|
175
|
+
display_name: z.string().min(1),
|
|
176
|
+
models: z.array(ModelDefZ).min(1, "At least one model is required"),
|
|
177
|
+
auth_hint: z.string().optional()
|
|
178
|
+
});
|
|
179
|
+
function computeRegistryVersion(registry) {
|
|
180
|
+
const sortedProviders = [...registry.providers].sort((a, b) => a.providerId.localeCompare(b.providerId)).map((provider) => ({
|
|
181
|
+
authHint: provider.authHint,
|
|
182
|
+
displayName: provider.displayName,
|
|
183
|
+
modelMappings: provider.modelMappings,
|
|
184
|
+
models: [...provider.models || []].sort((a, b) => a.modelId.localeCompare(b.modelId)).map((model) => ({
|
|
185
|
+
capabilities: [...model.capabilities].sort(),
|
|
186
|
+
costSchema: model.costSchema,
|
|
187
|
+
displayName: model.displayName,
|
|
188
|
+
modelId: model.modelId,
|
|
189
|
+
unpricedReason: model.unpricedReason
|
|
190
|
+
})),
|
|
191
|
+
providerId: provider.providerId
|
|
192
|
+
}));
|
|
193
|
+
const canonical = JSON.stringify(sortJsonValue(sortedProviders));
|
|
194
|
+
return crypto.createHash("sha256").update(canonical).digest("hex");
|
|
195
|
+
}
|
|
196
|
+
function sortJsonValue(value) {
|
|
197
|
+
if (Array.isArray(value)) return value.map(sortJsonValue);
|
|
198
|
+
if (value && typeof value === "object") {
|
|
199
|
+
return Object.fromEntries(
|
|
200
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, sortJsonValue(child)])
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
return value;
|
|
204
|
+
}
|
|
205
|
+
function buildRegistry(providers) {
|
|
206
|
+
const sorted = [...providers].sort((a, b) => a.providerId.localeCompare(b.providerId));
|
|
207
|
+
const registry = {
|
|
208
|
+
providers: sorted,
|
|
209
|
+
registryVersion: ""
|
|
210
|
+
// Placeholder, will be computed
|
|
211
|
+
};
|
|
212
|
+
const versionData = { ...registry, registryVersion: "" };
|
|
213
|
+
registry.registryVersion = computeRegistryVersion(versionData);
|
|
214
|
+
return registry;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/cost-tracking/list-price-rates.ts
|
|
218
|
+
var PER_1K_TO_PER_1M = 1e3;
|
|
219
|
+
function nonEmptyString(value) {
|
|
220
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
221
|
+
}
|
|
222
|
+
function metaRecord(row) {
|
|
223
|
+
return row._meta && typeof row._meta === "object" ? row._meta : {};
|
|
224
|
+
}
|
|
225
|
+
function pricingSourceFromRow(row) {
|
|
226
|
+
const meta = metaRecord(row);
|
|
227
|
+
return nonEmptyString(row.pricing_source) ?? nonEmptyString(meta.pricing_source) ?? nonEmptyString(meta.source);
|
|
228
|
+
}
|
|
229
|
+
function billingSourceFromRow(row) {
|
|
230
|
+
return nonEmptyString(row.billing_source) ?? nonEmptyString(metaRecord(row).billing_source);
|
|
231
|
+
}
|
|
232
|
+
function pricingConfidenceFromRow(row) {
|
|
233
|
+
const meta = metaRecord(row);
|
|
234
|
+
const value = row.pricing_confidence ?? meta.pricing_confidence ?? meta.match_confidence;
|
|
235
|
+
return value === "high" || value === "medium" || value === "low" ? value : void 0;
|
|
236
|
+
}
|
|
237
|
+
function longContextFromRow(value) {
|
|
238
|
+
if (!value) return void 0;
|
|
239
|
+
return {
|
|
240
|
+
thresholdTokens: value.threshold_tokens,
|
|
241
|
+
inputMultiplier: value.input_multiplier,
|
|
242
|
+
outputMultiplier: value.output_multiplier,
|
|
243
|
+
...value.cache_read_multiplier !== void 0 ? { cacheReadMultiplier: value.cache_read_multiplier } : {},
|
|
244
|
+
...value.cache_write_multiplier !== void 0 ? { cacheWriteMultiplier: value.cache_write_multiplier } : {}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function rateCardFromPricingRow(row) {
|
|
248
|
+
const inputPer1m = row.input_per_1m_tokens ?? (row.input_per_1k_tokens === void 0 ? void 0 : row.input_per_1k_tokens * PER_1K_TO_PER_1M);
|
|
249
|
+
const outputPer1m = row.output_per_1m_tokens ?? (row.output_per_1k_tokens === void 0 ? void 0 : row.output_per_1k_tokens * PER_1K_TO_PER_1M);
|
|
250
|
+
if (inputPer1m === void 0 || outputPer1m === void 0) return void 0;
|
|
251
|
+
const longContext = longContextFromRow(row.long_context_pricing);
|
|
252
|
+
const speedTiers = speedTiersFromRow(row);
|
|
253
|
+
return {
|
|
254
|
+
inputPer1m,
|
|
255
|
+
outputPer1m,
|
|
256
|
+
...row.cache_read_per_1m_tokens !== void 0 ? { cacheReadPer1m: row.cache_read_per_1m_tokens } : {},
|
|
257
|
+
...row.cache_write_per_1m_tokens !== void 0 ? { cacheWritePer1m: row.cache_write_per_1m_tokens } : {},
|
|
258
|
+
...row.cache_write_1h_per_1m_tokens !== void 0 ? { cacheWriteLongPer1m: row.cache_write_1h_per_1m_tokens } : {},
|
|
259
|
+
...longContext ? { longContext } : {},
|
|
260
|
+
...speedTiers ? { speedTiers } : {}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function speedTiersFromRow(row) {
|
|
264
|
+
const entries = Object.entries(row.speed_tiers ?? {});
|
|
265
|
+
if (entries.length === 0) return void 0;
|
|
266
|
+
return Object.fromEntries(
|
|
267
|
+
entries.map(([name, tier]) => {
|
|
268
|
+
const longContext = longContextFromRow(tier.long_context_pricing);
|
|
269
|
+
return [
|
|
270
|
+
name,
|
|
271
|
+
{
|
|
272
|
+
inputPer1m: tier.input_per_1m_tokens,
|
|
273
|
+
outputPer1m: tier.output_per_1m_tokens,
|
|
274
|
+
...tier.cache_read_per_1m_tokens !== void 0 ? { cacheReadPer1m: tier.cache_read_per_1m_tokens } : {},
|
|
275
|
+
...tier.cache_write_per_1m_tokens !== void 0 ? { cacheWritePer1m: tier.cache_write_per_1m_tokens } : {},
|
|
276
|
+
...tier.cache_write_1h_per_1m_tokens !== void 0 ? { cacheWriteLongPer1m: tier.cache_write_1h_per_1m_tokens } : {},
|
|
277
|
+
...longContext ? { longContext } : {}
|
|
278
|
+
}
|
|
279
|
+
];
|
|
280
|
+
})
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
function pricingRowAtSpeedTier(row, tierName) {
|
|
284
|
+
const tier = row.speed_tiers?.[tierName];
|
|
285
|
+
if (!tier) return row;
|
|
286
|
+
const { speed_tiers: _speedTiers, long_context_pricing: _longContext, ...rest } = row;
|
|
287
|
+
return {
|
|
288
|
+
...rest,
|
|
289
|
+
input_per_1m_tokens: tier.input_per_1m_tokens,
|
|
290
|
+
output_per_1m_tokens: tier.output_per_1m_tokens,
|
|
291
|
+
cache_read_per_1m_tokens: tier.cache_read_per_1m_tokens,
|
|
292
|
+
cache_write_per_1m_tokens: tier.cache_write_per_1m_tokens,
|
|
293
|
+
cache_write_1h_per_1m_tokens: tier.cache_write_1h_per_1m_tokens,
|
|
294
|
+
input_per_1k_tokens: void 0,
|
|
295
|
+
output_per_1k_tokens: void 0,
|
|
296
|
+
...tier.long_context_pricing ? { long_context_pricing: tier.long_context_pricing } : {}
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function numberOrUndefined(value) {
|
|
300
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
301
|
+
}
|
|
302
|
+
function longContextFromSnapshot(value) {
|
|
303
|
+
const thresholdTokens = numberOrUndefined(value?.threshold_tokens);
|
|
304
|
+
if (!value || thresholdTokens === void 0) return void 0;
|
|
305
|
+
const cacheReadMultiplier = numberOrUndefined(value.cache_read_multiplier);
|
|
306
|
+
const cacheWriteMultiplier = numberOrUndefined(value.cache_write_multiplier);
|
|
307
|
+
return {
|
|
308
|
+
thresholdTokens,
|
|
309
|
+
inputMultiplier: numberOrUndefined(value.input_multiplier) ?? 1,
|
|
310
|
+
outputMultiplier: numberOrUndefined(value.output_multiplier) ?? 1,
|
|
311
|
+
...cacheReadMultiplier !== void 0 ? { cacheReadMultiplier } : {},
|
|
312
|
+
...cacheWriteMultiplier !== void 0 ? { cacheWriteMultiplier } : {}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function ratesFromSnapshot(schema) {
|
|
316
|
+
const cacheRead = numberOrUndefined(schema.cache_read_per_1k);
|
|
317
|
+
const cacheWrite = numberOrUndefined(schema.cache_write_per_1k);
|
|
318
|
+
const cacheWriteLong = numberOrUndefined(schema.cache_write_1h_per_1k);
|
|
319
|
+
const longContext = longContextFromSnapshot(schema.long_context_pricing);
|
|
320
|
+
return {
|
|
321
|
+
inputPer1m: (schema.prompt_per_1k ?? 0) * PER_1K_TO_PER_1M,
|
|
322
|
+
outputPer1m: (schema.completion_per_1k ?? 0) * PER_1K_TO_PER_1M,
|
|
323
|
+
...cacheRead !== void 0 ? { cacheReadPer1m: cacheRead * PER_1K_TO_PER_1M } : {},
|
|
324
|
+
...cacheWrite !== void 0 ? { cacheWritePer1m: cacheWrite * PER_1K_TO_PER_1M } : {},
|
|
325
|
+
...cacheWriteLong !== void 0 ? { cacheWriteLongPer1m: cacheWriteLong * PER_1K_TO_PER_1M } : {},
|
|
326
|
+
...longContext ? { longContext } : {}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function rateCardFromSnapshotCostSchema(schema) {
|
|
330
|
+
const tiers = Object.entries(schema.speed_tiers ?? {});
|
|
331
|
+
return {
|
|
332
|
+
...ratesFromSnapshot(schema),
|
|
333
|
+
...tiers.length > 0 ? {
|
|
334
|
+
speedTiers: Object.fromEntries(
|
|
335
|
+
tiers.map(([name, tier]) => [name, ratesFromSnapshot(tier)])
|
|
336
|
+
)
|
|
337
|
+
} : {}
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function per1kFromRates(rates) {
|
|
341
|
+
return {
|
|
342
|
+
prompt_per_1k: rates.inputPer1m / PER_1K_TO_PER_1M,
|
|
343
|
+
completion_per_1k: rates.outputPer1m / PER_1K_TO_PER_1M,
|
|
344
|
+
...rates.cacheReadPer1m !== void 0 ? { cache_read_per_1k: rates.cacheReadPer1m / PER_1K_TO_PER_1M } : {},
|
|
345
|
+
...rates.cacheWritePer1m !== void 0 ? { cache_write_per_1k: rates.cacheWritePer1m / PER_1K_TO_PER_1M } : {},
|
|
346
|
+
...rates.cacheWriteLongPer1m !== void 0 ? { cache_write_1h_per_1k: rates.cacheWriteLongPer1m / PER_1K_TO_PER_1M } : {}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function snapshotCostSchemaFromRow(row) {
|
|
350
|
+
const card = rateCardFromPricingRow(row);
|
|
351
|
+
if (!card) return void 0;
|
|
352
|
+
const speedTiers = Object.entries(row.speed_tiers ?? {}).map(([name, tier]) => [
|
|
353
|
+
name,
|
|
354
|
+
{
|
|
355
|
+
...per1kFromRates({
|
|
356
|
+
inputPer1m: tier.input_per_1m_tokens,
|
|
357
|
+
outputPer1m: tier.output_per_1m_tokens,
|
|
358
|
+
cacheReadPer1m: tier.cache_read_per_1m_tokens,
|
|
359
|
+
cacheWritePer1m: tier.cache_write_per_1m_tokens,
|
|
360
|
+
cacheWriteLongPer1m: tier.cache_write_1h_per_1m_tokens
|
|
361
|
+
}),
|
|
362
|
+
...tier.long_context_pricing ? { long_context_pricing: tier.long_context_pricing } : {}
|
|
363
|
+
}
|
|
364
|
+
]);
|
|
365
|
+
return {
|
|
366
|
+
...per1kFromRates(card),
|
|
367
|
+
// A row that states per-1k rates keeps them exactly instead of a round trip.
|
|
368
|
+
...row.input_per_1m_tokens === void 0 ? { prompt_per_1k: row.input_per_1k_tokens } : {},
|
|
369
|
+
...row.output_per_1m_tokens === void 0 ? { completion_per_1k: row.output_per_1k_tokens } : {},
|
|
370
|
+
...row.long_context_pricing ? { long_context_pricing: row.long_context_pricing } : {},
|
|
371
|
+
...speedTiers.length > 0 ? { speed_tiers: Object.fromEntries(speedTiers) } : {}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
function rateCardFromModelPricing(pricing) {
|
|
375
|
+
return {
|
|
376
|
+
inputPer1m: pricing.inputTokens,
|
|
377
|
+
outputPer1m: pricing.outputTokens,
|
|
378
|
+
cacheReadPer1m: pricing.cacheRead,
|
|
379
|
+
cacheWritePer1m: pricing.cacheWrite,
|
|
380
|
+
cacheWriteLongPer1m: pricing.cacheWriteLong,
|
|
381
|
+
longContext: pricing.longContextPricing,
|
|
382
|
+
speedTiers: pricing.speedTiers
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/core/providers/cursor/model-discovery.ts
|
|
387
|
+
import { execFileSync } from "child_process";
|
|
388
|
+
import { readFileSync as readFileSync2, statSync, unlinkSync } from "fs";
|
|
389
|
+
import * as fs2 from "fs/promises";
|
|
390
|
+
import { homedir } from "os";
|
|
391
|
+
import * as path3 from "path";
|
|
392
|
+
|
|
393
|
+
// src/core/providers/cursor/model-cache-path.ts
|
|
394
|
+
import path2 from "path";
|
|
395
|
+
function cursorModelCacheDir(options = {}) {
|
|
396
|
+
const projectRoot = options.projectRoot ?? getBuildProjectRoot();
|
|
397
|
+
return path2.join(
|
|
398
|
+
options.modelCacheDir ?? path2.join(resolveSharedRefreshPaths().storageRoot, "cache", "models"),
|
|
399
|
+
options.inventoryContext ?? refreshInventoryContext(projectRoot),
|
|
400
|
+
"providers"
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// src/core/providers/cursor/model-discovery.ts
|
|
405
|
+
var DEFAULT_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
406
|
+
function getCacheTTL() {
|
|
407
|
+
const envTTL = process.env.BUILD_CURSOR_CACHE_TTL_MS;
|
|
408
|
+
if (envTTL) {
|
|
409
|
+
const parsed = Number.parseInt(envTTL, 10);
|
|
410
|
+
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
411
|
+
return parsed;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return DEFAULT_CACHE_TTL_MS;
|
|
415
|
+
}
|
|
416
|
+
function getDefaultCacheDir() {
|
|
417
|
+
return cursorModelCacheDir();
|
|
418
|
+
}
|
|
419
|
+
function getCacheFilePath(cacheDir) {
|
|
420
|
+
return path3.join(cacheDir, "cursor-models.json");
|
|
421
|
+
}
|
|
422
|
+
function createCursorDiscoveryChildEnv() {
|
|
423
|
+
const env = createScopedProviderChildEnv("cursor");
|
|
424
|
+
const apiKey = getCursorApiKeyFromEnv();
|
|
425
|
+
if (apiKey) {
|
|
426
|
+
env.CURSOR_API_KEY = apiKey;
|
|
427
|
+
}
|
|
428
|
+
return env;
|
|
429
|
+
}
|
|
430
|
+
function getCursorApiKeyFromEnv() {
|
|
431
|
+
const candidate = process.env.CURSOR_API_KEY?.trim();
|
|
432
|
+
if (candidate && candidate.length > 0) return candidate;
|
|
433
|
+
try {
|
|
434
|
+
const credPath = path3.join(homedir(), ".build", "credentials", "cursor.json");
|
|
435
|
+
const raw = readFileSync2(credPath, "utf-8");
|
|
436
|
+
const creds = JSON.parse(raw);
|
|
437
|
+
const key = (typeof creds.apiKey === "string" ? creds.apiKey : void 0) ?? (typeof creds.api_key === "string" ? creds.api_key : void 0);
|
|
438
|
+
if (key && key.trim().length > 0) return key.trim();
|
|
439
|
+
} catch {
|
|
440
|
+
}
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
function inferCursorModelProvider(modelId) {
|
|
444
|
+
const normalized = modelId.trim().toLowerCase();
|
|
445
|
+
if (normalized === "auto" || normalized === "cheetah" || normalized === "grok" || normalized.startsWith("grok-") || normalized.startsWith("composer") || normalized.startsWith("cursor-grok-") || normalized.startsWith("cursor-")) {
|
|
446
|
+
return "cursor";
|
|
447
|
+
}
|
|
448
|
+
if (normalized.startsWith("claude") || normalized.startsWith("sonnet") || normalized.startsWith("opus") || normalized.startsWith("haiku")) {
|
|
449
|
+
return "claude";
|
|
450
|
+
}
|
|
451
|
+
if (normalized.startsWith("gpt") || normalized.startsWith("o1") || normalized.startsWith("o3")) {
|
|
452
|
+
return "codex";
|
|
453
|
+
}
|
|
454
|
+
return "unknown";
|
|
455
|
+
}
|
|
456
|
+
function normalizeApiModels(payload) {
|
|
457
|
+
const candidates = [];
|
|
458
|
+
const collect = (value) => {
|
|
459
|
+
if (!value) return;
|
|
460
|
+
if (typeof value === "string") {
|
|
461
|
+
const trimmed = value.trim();
|
|
462
|
+
if (trimmed) candidates.push(trimmed);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (Array.isArray(value)) {
|
|
466
|
+
for (const entry of value) collect(entry);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (typeof value === "object") {
|
|
470
|
+
const obj = value;
|
|
471
|
+
const idCandidate = typeof obj.id === "string" && obj.id || typeof obj.model === "string" && obj.model || typeof obj.slug === "string" && obj.slug || typeof obj.name === "string" && obj.name;
|
|
472
|
+
if (idCandidate) {
|
|
473
|
+
candidates.push(idCandidate.trim());
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
if (Array.isArray(payload)) {
|
|
478
|
+
collect(payload);
|
|
479
|
+
} else if (payload && typeof payload === "object") {
|
|
480
|
+
const obj = payload;
|
|
481
|
+
collect(obj.models);
|
|
482
|
+
collect(obj.data);
|
|
483
|
+
collect(obj.results);
|
|
484
|
+
}
|
|
485
|
+
const seen = /* @__PURE__ */ new Set();
|
|
486
|
+
const models = [];
|
|
487
|
+
for (const id of candidates) {
|
|
488
|
+
if (!id || seen.has(id)) continue;
|
|
489
|
+
seen.add(id);
|
|
490
|
+
models.push({
|
|
491
|
+
id,
|
|
492
|
+
provider: inferCursorModelProvider(id),
|
|
493
|
+
isDefault: id === "auto"
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
return models;
|
|
497
|
+
}
|
|
498
|
+
function isBareCursorModelIdLine(value) {
|
|
499
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(value)) {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
const normalized = value.toLowerCase();
|
|
503
|
+
return normalized.includes("-") || normalized === "auto" || normalized === "cheetah" || normalized === "grok";
|
|
504
|
+
}
|
|
505
|
+
function parseCursorModelOutput(output) {
|
|
506
|
+
const trimmed = output.trim();
|
|
507
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
508
|
+
try {
|
|
509
|
+
const fromJson = normalizeApiModels(JSON.parse(trimmed));
|
|
510
|
+
if (fromJson.length > 0) {
|
|
511
|
+
return fromJson;
|
|
512
|
+
}
|
|
513
|
+
} catch {
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const models = [];
|
|
517
|
+
const seen = /* @__PURE__ */ new Set();
|
|
518
|
+
const lines = output.split("\n");
|
|
519
|
+
let currentProvider = "unknown";
|
|
520
|
+
for (const line of lines) {
|
|
521
|
+
const plainMatch = line.match(/^([a-zA-Z0-9][a-zA-Z0-9._-]*)\s+-\s+(.+)/);
|
|
522
|
+
if (plainMatch) {
|
|
523
|
+
const modelId = (plainMatch[1] ?? "").trim();
|
|
524
|
+
const displayName = (plainMatch[2] ?? "").trim();
|
|
525
|
+
if (modelId && !seen.has(modelId)) {
|
|
526
|
+
seen.add(modelId);
|
|
527
|
+
models.push({
|
|
528
|
+
id: modelId,
|
|
529
|
+
provider: inferCursorModelProvider(modelId),
|
|
530
|
+
displayName: displayName.length > 0 ? displayName : void 0,
|
|
531
|
+
isDefault: line.includes("(default)"),
|
|
532
|
+
isLegacy: false
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
const bareId = line.trim();
|
|
538
|
+
if (isBareCursorModelIdLine(bareId) && !seen.has(bareId)) {
|
|
539
|
+
seen.add(bareId);
|
|
540
|
+
models.push({
|
|
541
|
+
id: bareId,
|
|
542
|
+
provider: inferCursorModelProvider(bareId),
|
|
543
|
+
isDefault: bareId === "auto",
|
|
544
|
+
isLegacy: false
|
|
545
|
+
});
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (line.includes("### **Claude**")) {
|
|
549
|
+
currentProvider = "claude";
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
if (line.includes("### **Codex**")) {
|
|
553
|
+
currentProvider = "codex";
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
if (line.includes("### **Cursor**")) {
|
|
557
|
+
currentProvider = "cursor";
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
const bulletMatch = line.match(/^[-*]\s+(.+)/);
|
|
561
|
+
if (bulletMatch) {
|
|
562
|
+
const content = bulletMatch[1];
|
|
563
|
+
if (!content) continue;
|
|
564
|
+
const isDefault = line.includes("(default)");
|
|
565
|
+
const isLegacy = line.toLowerCase().includes("legacy");
|
|
566
|
+
const backtickMatches = content.matchAll(/`([^`]+)`/g);
|
|
567
|
+
for (const match of backtickMatches) {
|
|
568
|
+
const modelId = match[1]?.trim();
|
|
569
|
+
if (modelId && modelId.toLowerCase() !== "legacy:" && !seen.has(modelId)) {
|
|
570
|
+
seen.add(modelId);
|
|
571
|
+
models.push({
|
|
572
|
+
id: modelId,
|
|
573
|
+
provider: currentProvider === "unknown" ? inferCursorModelProvider(modelId) : currentProvider,
|
|
574
|
+
isDefault,
|
|
575
|
+
isLegacy
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return models;
|
|
582
|
+
}
|
|
583
|
+
async function executeCursorCLI(cliCommand) {
|
|
584
|
+
const { command: resolvedCommand, prefixArgs } = cliCommand ?? (await import("./resolve-provider-cli-ZEXVZKET.js")).resolveProviderCliCommand("cursor");
|
|
585
|
+
try {
|
|
586
|
+
let version;
|
|
587
|
+
try {
|
|
588
|
+
const sandboxed = wrapWithSandbox({
|
|
589
|
+
workingDir: process.cwd(),
|
|
590
|
+
command: resolvedCommand,
|
|
591
|
+
args: [...prefixArgs, "--version"],
|
|
592
|
+
providerName: "Cursor"
|
|
593
|
+
});
|
|
594
|
+
const versionOutput = execFileSync(sandboxed.command, sandboxed.args, {
|
|
595
|
+
encoding: "utf-8",
|
|
596
|
+
timeout: 5e3,
|
|
597
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
598
|
+
env: createCursorDiscoveryChildEnv()
|
|
599
|
+
}).trim();
|
|
600
|
+
version = versionOutput;
|
|
601
|
+
} catch {
|
|
602
|
+
}
|
|
603
|
+
const listingArgSets = [
|
|
604
|
+
[...prefixArgs, "models"],
|
|
605
|
+
[...prefixArgs, "--list-models"]
|
|
606
|
+
];
|
|
607
|
+
let lastError;
|
|
608
|
+
for (const listingArgs of listingArgSets) {
|
|
609
|
+
try {
|
|
610
|
+
const sandboxed = wrapWithSandbox({
|
|
611
|
+
workingDir: process.cwd(),
|
|
612
|
+
command: resolvedCommand,
|
|
613
|
+
args: listingArgs,
|
|
614
|
+
providerName: "Cursor"
|
|
615
|
+
});
|
|
616
|
+
const output = execFileSync(sandboxed.command, sandboxed.args, {
|
|
617
|
+
encoding: "utf-8",
|
|
618
|
+
timeout: 1e4,
|
|
619
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
620
|
+
env: createCursorDiscoveryChildEnv()
|
|
621
|
+
});
|
|
622
|
+
const models = parseCursorModelOutput(output);
|
|
623
|
+
if (models.length > 0) {
|
|
624
|
+
return { models, version };
|
|
625
|
+
}
|
|
626
|
+
lastError = new Error("No models found in cursor-agent models output");
|
|
627
|
+
} catch (error) {
|
|
628
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
throw lastError ?? new Error("No models found in cursor-agent models output");
|
|
632
|
+
} catch (error) {
|
|
633
|
+
throw new Error(
|
|
634
|
+
`Failed to execute cursor-agent CLI: ${error instanceof Error ? error.message : String(error)}`
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
async function executeCursorApi(apiKey) {
|
|
639
|
+
const controller = new AbortController();
|
|
640
|
+
const timeout = setTimeout(() => controller.abort(), 12e3);
|
|
641
|
+
try {
|
|
642
|
+
const auth = Buffer.from(`${apiKey}:`, "utf8").toString("base64");
|
|
643
|
+
const response = await fetch("https://api.cursor.com/v0/models", {
|
|
644
|
+
method: "GET",
|
|
645
|
+
headers: {
|
|
646
|
+
Authorization: `Basic ${auth}`,
|
|
647
|
+
Accept: "application/json"
|
|
648
|
+
},
|
|
649
|
+
signal: controller.signal
|
|
650
|
+
});
|
|
651
|
+
if (!response.ok) {
|
|
652
|
+
const body = await response.text().catch(() => "");
|
|
653
|
+
throw new Error(`HTTP ${response.status}: ${body || response.statusText}`);
|
|
654
|
+
}
|
|
655
|
+
const parsed = await response.json();
|
|
656
|
+
const models = normalizeApiModels(parsed);
|
|
657
|
+
if (models.length === 0) {
|
|
658
|
+
throw new Error("No models found in Cursor API response");
|
|
659
|
+
}
|
|
660
|
+
return { models };
|
|
661
|
+
} catch (error) {
|
|
662
|
+
throw new Error(
|
|
663
|
+
`Failed to fetch Cursor API models: ${error instanceof Error ? error.message : String(error)}`
|
|
664
|
+
);
|
|
665
|
+
} finally {
|
|
666
|
+
clearTimeout(timeout);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
async function readCache(cacheFilePath) {
|
|
670
|
+
try {
|
|
671
|
+
const content = await fs2.readFile(cacheFilePath, "utf-8");
|
|
672
|
+
const cached = JSON.parse(content);
|
|
673
|
+
if (!cached.models || !Array.isArray(cached.models) || !cached.timestamp) {
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
const age = Date.now() - cached.timestamp;
|
|
677
|
+
const ttl = getCacheTTL();
|
|
678
|
+
if (age > ttl) {
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
return cached;
|
|
682
|
+
} catch {
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
async function writeCache(cacheFilePath, result) {
|
|
687
|
+
try {
|
|
688
|
+
const cacheDir = path3.dirname(cacheFilePath);
|
|
689
|
+
await fs2.mkdir(cacheDir, { recursive: true });
|
|
690
|
+
await fs2.writeFile(cacheFilePath, JSON.stringify(result, null, 2), "utf-8");
|
|
691
|
+
} catch (error) {
|
|
692
|
+
console.warn(
|
|
693
|
+
`[Cursor Model Discovery] Failed to write cache: ${error instanceof Error ? error.message : String(error)}`
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
async function discoverCursorModels(options) {
|
|
698
|
+
const { forceRefresh = false, cacheDir = getDefaultCacheDir(), cliOnly = false } = options || {};
|
|
699
|
+
const cacheFilePath = getCacheFilePath(cacheDir);
|
|
700
|
+
if (!forceRefresh) {
|
|
701
|
+
const cached = await readCache(cacheFilePath);
|
|
702
|
+
if (cached) {
|
|
703
|
+
return cached;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
const apiKey = cliOnly ? null : getCursorApiKeyFromEnv();
|
|
707
|
+
if (apiKey) {
|
|
708
|
+
try {
|
|
709
|
+
const { models } = await executeCursorApi(apiKey);
|
|
710
|
+
const result = {
|
|
711
|
+
models,
|
|
712
|
+
source: "api",
|
|
713
|
+
timestamp: Date.now(),
|
|
714
|
+
canonicalMappings: buildCanonicalMappings(models)
|
|
715
|
+
};
|
|
716
|
+
rememberAuthoritativeDiscovery(cacheFilePath, result);
|
|
717
|
+
await writeCache(cacheFilePath, result);
|
|
718
|
+
return result;
|
|
719
|
+
} catch (error) {
|
|
720
|
+
console.warn(
|
|
721
|
+
`[Cursor Model Discovery] API discovery failed, falling back to CLI: ${error instanceof Error ? error.message : String(error)}`
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
try {
|
|
726
|
+
const { models, version } = await executeCursorCLI(options?.cliCommand);
|
|
727
|
+
const result = {
|
|
728
|
+
models,
|
|
729
|
+
source: "cli",
|
|
730
|
+
timestamp: Date.now(),
|
|
731
|
+
cliVersion: version,
|
|
732
|
+
canonicalMappings: buildCanonicalMappings(models)
|
|
733
|
+
};
|
|
734
|
+
rememberAuthoritativeDiscovery(cacheFilePath, result);
|
|
735
|
+
await writeCache(cacheFilePath, result);
|
|
736
|
+
return result;
|
|
737
|
+
} catch (error) {
|
|
738
|
+
console.warn(
|
|
739
|
+
`[Cursor Model Discovery] CLI execution failed, using fallback: ${error instanceof Error ? error.message : String(error)}`
|
|
740
|
+
);
|
|
741
|
+
const result = {
|
|
742
|
+
models: CURSOR_KNOWN_MODELS,
|
|
743
|
+
source: "fallback",
|
|
744
|
+
timestamp: Date.now(),
|
|
745
|
+
canonicalMappings: buildCanonicalMappings(CURSOR_KNOWN_MODELS)
|
|
746
|
+
};
|
|
747
|
+
return result;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
var cachedSnapshot = null;
|
|
751
|
+
var cachedMappingSnapshot = null;
|
|
752
|
+
function uniqueDiscoveredModelIds(models) {
|
|
753
|
+
return Array.from(
|
|
754
|
+
new Set(
|
|
755
|
+
models.map((model) => model?.id).filter((id) => typeof id === "string" && id.trim().length > 0)
|
|
756
|
+
)
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
function rememberAuthoritativeDiscovery(cachePath, result) {
|
|
760
|
+
if (result.source !== "api" && result.source !== "cli") {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
cachedSnapshot = {
|
|
764
|
+
cachePath,
|
|
765
|
+
mtimeMs: Date.now(),
|
|
766
|
+
modelIds: uniqueDiscoveredModelIds(result.models)
|
|
767
|
+
};
|
|
768
|
+
cachedMappingSnapshot = {
|
|
769
|
+
cachePath,
|
|
770
|
+
mtimeMs: Date.now(),
|
|
771
|
+
mappings: { ...result.canonicalMappings ?? {} }
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
function getCachedCursorModelIds(options) {
|
|
775
|
+
const cacheDir = options?.cacheDir ?? getDefaultCacheDir();
|
|
776
|
+
const cachePath = getCacheFilePath(cacheDir);
|
|
777
|
+
const ttl = getCacheTTL();
|
|
778
|
+
try {
|
|
779
|
+
const stat = statSync(cachePath);
|
|
780
|
+
if (Date.now() - stat.mtimeMs > ttl) {
|
|
781
|
+
if (cachedSnapshot?.cachePath === cachePath) {
|
|
782
|
+
cachedSnapshot = null;
|
|
783
|
+
}
|
|
784
|
+
return [];
|
|
785
|
+
}
|
|
786
|
+
if (cachedSnapshot && cachedSnapshot.cachePath === cachePath && cachedSnapshot.mtimeMs === stat.mtimeMs) {
|
|
787
|
+
return [...cachedSnapshot.modelIds];
|
|
788
|
+
}
|
|
789
|
+
const raw = readFileSync2(cachePath, "utf-8");
|
|
790
|
+
const parsed = JSON.parse(raw);
|
|
791
|
+
const unique = Array.isArray(parsed?.models) ? uniqueDiscoveredModelIds(parsed.models) : [];
|
|
792
|
+
cachedSnapshot = {
|
|
793
|
+
cachePath,
|
|
794
|
+
mtimeMs: stat.mtimeMs,
|
|
795
|
+
modelIds: unique
|
|
796
|
+
};
|
|
797
|
+
return [...unique];
|
|
798
|
+
} catch (error) {
|
|
799
|
+
if (error?.code === "ENOENT") {
|
|
800
|
+
if (cachedSnapshot?.cachePath === cachePath) {
|
|
801
|
+
return [...cachedSnapshot.modelIds];
|
|
802
|
+
}
|
|
803
|
+
return [];
|
|
804
|
+
}
|
|
805
|
+
if (cachedSnapshot?.cachePath === cachePath) {
|
|
806
|
+
cachedSnapshot = null;
|
|
807
|
+
}
|
|
808
|
+
try {
|
|
809
|
+
unlinkSync(cachePath);
|
|
810
|
+
} catch {
|
|
811
|
+
}
|
|
812
|
+
logWarning(
|
|
813
|
+
"cursor-model-discovery",
|
|
814
|
+
`Ignoring corrupt model cache at ${cachePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
815
|
+
);
|
|
816
|
+
return [];
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
var LEGACY_CURSOR_CLAUDE_PATTERN = /^claude-(\d+(?:\.\d+)?)-(\w+?)(?:-([\w-]+))?$/;
|
|
820
|
+
var CURRENT_CURSOR_CLAUDE_PATTERN = /^claude-(opus|sonnet|fable)-(\d+(?:-\d+)*)(?:-([\w-]+))?$/;
|
|
821
|
+
function parseCursorClaudeModelId(modelId) {
|
|
822
|
+
const current = modelId.match(CURRENT_CURSOR_CLAUDE_PATTERN);
|
|
823
|
+
if (current) {
|
|
824
|
+
return {
|
|
825
|
+
tier: current[1] ?? "",
|
|
826
|
+
version: (current[2] ?? "").replaceAll("-", "."),
|
|
827
|
+
suffix: current[3] ?? ""
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
const legacy = modelId.match(LEGACY_CURSOR_CLAUDE_PATTERN);
|
|
831
|
+
if (!legacy) return void 0;
|
|
832
|
+
return { version: legacy[1] ?? "", tier: legacy[2] ?? "", suffix: legacy[3] ?? "" };
|
|
833
|
+
}
|
|
834
|
+
function buildCanonicalMappings(models) {
|
|
835
|
+
const mappings = {};
|
|
836
|
+
const canonicalRankings = /* @__PURE__ */ new Map();
|
|
837
|
+
for (const model of models) {
|
|
838
|
+
const parsed = parseCursorClaudeModelId(model.id);
|
|
839
|
+
if (!parsed) continue;
|
|
840
|
+
const { version, tier, suffix } = parsed;
|
|
841
|
+
if (suffix.includes("thinking")) continue;
|
|
842
|
+
const canonical = `${tier}-${version}`;
|
|
843
|
+
const baseRank = tier === "opus" ? ["high", "", "medium", "low", "xhigh", "max"].indexOf(suffix) : ["medium", "", "low", "high", "xhigh", "max"].indexOf(suffix);
|
|
844
|
+
const rank = baseRank === -1 ? Number.MAX_SAFE_INTEGER : baseRank;
|
|
845
|
+
if (suffix === "1m") {
|
|
846
|
+
const canonical1m = `${tier}-${version}-1m`;
|
|
847
|
+
if (!mappings[canonical1m]) {
|
|
848
|
+
mappings[canonical1m] = model.id;
|
|
849
|
+
}
|
|
850
|
+
} else {
|
|
851
|
+
const existingRank = canonicalRankings.get(canonical);
|
|
852
|
+
if (existingRank === void 0 || rank < existingRank) {
|
|
853
|
+
mappings[canonical] = model.id;
|
|
854
|
+
canonicalRankings.set(canonical, rank);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
return mappings;
|
|
859
|
+
}
|
|
860
|
+
function getCachedCanonicalMappings(options) {
|
|
861
|
+
const cacheDir = options?.cacheDir ?? getDefaultCacheDir();
|
|
862
|
+
const cachePath = getCacheFilePath(cacheDir);
|
|
863
|
+
const ttl = getCacheTTL();
|
|
864
|
+
try {
|
|
865
|
+
const stat = statSync(cachePath);
|
|
866
|
+
if (Date.now() - stat.mtimeMs > ttl) {
|
|
867
|
+
if (cachedMappingSnapshot?.cachePath === cachePath) cachedMappingSnapshot = null;
|
|
868
|
+
return {};
|
|
869
|
+
}
|
|
870
|
+
if (cachedMappingSnapshot && cachedMappingSnapshot.cachePath === cachePath && cachedMappingSnapshot.mtimeMs === stat.mtimeMs) {
|
|
871
|
+
return { ...cachedMappingSnapshot.mappings };
|
|
872
|
+
}
|
|
873
|
+
const raw = readFileSync2(cachePath, "utf-8");
|
|
874
|
+
const parsed = JSON.parse(raw);
|
|
875
|
+
const mappings = { ...parsed?.canonicalMappings ?? {} };
|
|
876
|
+
cachedMappingSnapshot = { cachePath, mtimeMs: stat.mtimeMs, mappings };
|
|
877
|
+
return { ...mappings };
|
|
878
|
+
} catch (error) {
|
|
879
|
+
if (error?.code === "ENOENT") {
|
|
880
|
+
if (cachedMappingSnapshot?.cachePath === cachePath) {
|
|
881
|
+
return { ...cachedMappingSnapshot.mappings };
|
|
882
|
+
}
|
|
883
|
+
return {};
|
|
884
|
+
}
|
|
885
|
+
if (cachedMappingSnapshot?.cachePath === cachePath) cachedMappingSnapshot = null;
|
|
886
|
+
try {
|
|
887
|
+
unlinkSync(cachePath);
|
|
888
|
+
} catch {
|
|
889
|
+
}
|
|
890
|
+
console.warn(
|
|
891
|
+
`[cursor-model-discovery] Ignoring corrupt model cache at ${cachePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
892
|
+
);
|
|
893
|
+
return {};
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// src/core/providers/cursor/config.ts
|
|
898
|
+
var CURSOR_FALLBACK_MODELS = CURSOR_KNOWN_MODELS.map((m) => m.id);
|
|
899
|
+
var GROK_FAST_PRICE_MODELS = {
|
|
900
|
+
"4.7": "grok-4.7-build-fast"
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
// src/core/providers/cursor/model-price-target.ts
|
|
904
|
+
var EFFORT_SUFFIX = /-(?:extra-high|xhigh|none|minimal|low|medium|high|max)$/;
|
|
905
|
+
var MAX_SUFFIX_STRIPS = 4;
|
|
906
|
+
var MAKER_ROUTE_PREFIX = { kimi: "moonshotai", glm: "z-ai" };
|
|
907
|
+
function stripVariantSuffixes(modelId) {
|
|
908
|
+
let base = modelId;
|
|
909
|
+
let fast = false;
|
|
910
|
+
for (let i = 0; i < MAX_SUFFIX_STRIPS; i++) {
|
|
911
|
+
if (base.endsWith("-fast")) {
|
|
912
|
+
fast = true;
|
|
913
|
+
base = base.slice(0, -"-fast".length);
|
|
914
|
+
} else if (base.endsWith("-thinking")) {
|
|
915
|
+
base = base.slice(0, -"-thinking".length);
|
|
916
|
+
} else if (EFFORT_SUFFIX.test(base)) {
|
|
917
|
+
base = base.replace(EFFORT_SUFFIX, "");
|
|
918
|
+
} else {
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
return { base, fast };
|
|
923
|
+
}
|
|
924
|
+
function resolveCursorMakerPriceTarget(modelId, hasRow = () => false) {
|
|
925
|
+
const { base, fast } = stripVariantSuffixes(modelId.toLowerCase());
|
|
926
|
+
const fastTier = fast ? { speed_tier: "fast" } : {};
|
|
927
|
+
const cursorFastRow = `${base}-fast`;
|
|
928
|
+
if (fast && hasRow("cursor", cursorFastRow)) {
|
|
929
|
+
return { provider: "cursor", model: cursorFastRow };
|
|
930
|
+
}
|
|
931
|
+
const grok = /^(?:cursor-)?grok-(\d+\.\d+)$/.exec(base);
|
|
932
|
+
if (grok) {
|
|
933
|
+
const version = grok[1];
|
|
934
|
+
const fastModel = fast && version ? GROK_FAST_PRICE_MODELS[version] : void 0;
|
|
935
|
+
if (fastModel) return { provider: "grok", model: fastModel };
|
|
936
|
+
return { provider: "grok", model: `grok-${version}` };
|
|
937
|
+
}
|
|
938
|
+
if (/^claude-(?:opus|sonnet|haiku|fable)-\d+(?:-\d+)?$/.test(base)) {
|
|
939
|
+
return { provider: "claude", model: base, ...fastTier };
|
|
940
|
+
}
|
|
941
|
+
const legacyClaude = /^claude-(\d+)(?:\.(\d+))?-(opus|sonnet|haiku)$/.exec(base);
|
|
942
|
+
if (legacyClaude) {
|
|
943
|
+
const [, major, minor, family] = legacyClaude;
|
|
944
|
+
const version = minor === void 0 ? major : `${major}-${minor}`;
|
|
945
|
+
return { provider: "claude", model: `claude-${family}-${version}`, ...fastTier };
|
|
946
|
+
}
|
|
947
|
+
if (/^gpt-\d/.test(base)) {
|
|
948
|
+
return { provider: "codex", model: base, ...fastTier };
|
|
949
|
+
}
|
|
950
|
+
if (/^gemini-/.test(base)) {
|
|
951
|
+
const preview = `${base}-preview`;
|
|
952
|
+
if (!hasRow("gemini", base) && hasRow("gemini", preview)) {
|
|
953
|
+
return {
|
|
954
|
+
provider: "gemini",
|
|
955
|
+
model: preview,
|
|
956
|
+
note: `${base} priced at the preview rate pending GA`
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
return { provider: "gemini", model: base };
|
|
960
|
+
}
|
|
961
|
+
const makerRoute = /^(kimi|glm)-/.exec(base);
|
|
962
|
+
if (makerRoute) {
|
|
963
|
+
const prefix = MAKER_ROUTE_PREFIX[makerRoute[1] ?? ""];
|
|
964
|
+
return prefix ? { provider: "openrouter", model: `${prefix}/${base}` } : null;
|
|
965
|
+
}
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// src/core/providers/model-update/pricing/passthrough-resolver.ts
|
|
970
|
+
var DEFAULT_CONFIG = {
|
|
971
|
+
maxDepth: 5,
|
|
972
|
+
strict: false
|
|
973
|
+
};
|
|
974
|
+
var DEFAULT_CURSOR_MAPPINGS = {
|
|
975
|
+
// Claude models
|
|
976
|
+
"sonnet-4.6": { provider: "claude", model: "claude-sonnet-4-6" },
|
|
977
|
+
"sonnet-4-6": { provider: "claude", model: "claude-sonnet-4-6" },
|
|
978
|
+
"claude-sonnet-4-6": { provider: "claude", model: "claude-sonnet-4-6" },
|
|
979
|
+
"sonnet-4.5": { provider: "claude", model: "claude-sonnet-4-5" },
|
|
980
|
+
"sonnet-4-5": { provider: "claude", model: "claude-sonnet-4-5" },
|
|
981
|
+
"claude-sonnet-4-5": { provider: "claude", model: "claude-sonnet-4-5" },
|
|
982
|
+
"opus-4.6": { provider: "claude", model: "claude-opus-4-6" },
|
|
983
|
+
"opus-4-6": { provider: "claude", model: "claude-opus-4-6" },
|
|
984
|
+
"claude-opus-4-6": { provider: "claude", model: "claude-opus-4-6" },
|
|
985
|
+
"haiku-4.5": { provider: "claude", model: "claude-haiku-4-5" },
|
|
986
|
+
"haiku-4-5": { provider: "claude", model: "claude-haiku-4-5" },
|
|
987
|
+
"claude-haiku-4-5": { provider: "claude", model: "claude-haiku-4-5" },
|
|
988
|
+
"claude-haiku-4-5-20251001": { provider: "claude", model: "claude-haiku-4-5" },
|
|
989
|
+
"opus-4.5": { provider: "claude", model: "claude-opus-4-5" },
|
|
990
|
+
"opus-4-5": { provider: "claude", model: "claude-opus-4-5" },
|
|
991
|
+
"claude-opus-4-5": { provider: "claude", model: "claude-opus-4-5" },
|
|
992
|
+
"claude-3.5-sonnet": { provider: "claude", model: "claude-3-5-sonnet" },
|
|
993
|
+
"claude-3-5-sonnet": { provider: "claude", model: "claude-3-5-sonnet" },
|
|
994
|
+
// OpenAI models
|
|
995
|
+
"gpt-5": { provider: "codex", model: "gpt-5" },
|
|
996
|
+
"gpt-5-mini": { provider: "codex", model: "gpt-5-mini" },
|
|
997
|
+
"gpt-4o": { provider: "codex", model: "gpt-4o" },
|
|
998
|
+
"gpt-4o-mini": { provider: "codex", model: "gpt-4o-mini" },
|
|
999
|
+
"gpt-4-turbo": { provider: "codex", model: "gpt-4-turbo" },
|
|
1000
|
+
o1: { provider: "codex", model: "o1" },
|
|
1001
|
+
"o1-mini": { provider: "codex", model: "o1-mini" },
|
|
1002
|
+
"o1-pro": { provider: "codex", model: "o1-pro" },
|
|
1003
|
+
o3: { provider: "codex", model: "o3" },
|
|
1004
|
+
"o3-mini": { provider: "codex", model: "o3-mini" },
|
|
1005
|
+
// Gemini models
|
|
1006
|
+
"gemini-2.5-pro": { provider: "gemini", model: "gemini-2.5-pro" },
|
|
1007
|
+
"gemini-2.5-flash": { provider: "gemini", model: "gemini-2.5-flash" },
|
|
1008
|
+
"gemini-2.0-flash": { provider: "gemini", model: "gemini-2.0-flash" },
|
|
1009
|
+
"gemini-1.5-pro": { provider: "gemini", model: "gemini-1.5-pro" },
|
|
1010
|
+
"gemini-1.5-flash": { provider: "gemini", model: "gemini-1.5-flash" }
|
|
1011
|
+
};
|
|
1012
|
+
var PassthroughResolver = class {
|
|
1013
|
+
config;
|
|
1014
|
+
customMappings = /* @__PURE__ */ new Map();
|
|
1015
|
+
constructor(config = {}) {
|
|
1016
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Register custom pass-through mappings for a provider.
|
|
1020
|
+
*/
|
|
1021
|
+
registerMappings(providerId, mappings) {
|
|
1022
|
+
this.customMappings.set(providerId.toLowerCase(), mappings);
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Resolve pricing for a model, following pass-through chains.
|
|
1026
|
+
*/
|
|
1027
|
+
resolvePricing(providerId, modelId, depth = 0, visited = /* @__PURE__ */ new Set()) {
|
|
1028
|
+
const key = `${providerId}:${modelId}`;
|
|
1029
|
+
if (visited.has(key)) {
|
|
1030
|
+
return {
|
|
1031
|
+
status: "unknown",
|
|
1032
|
+
modelId,
|
|
1033
|
+
reason: `Cycle detected in pass-through chain: ${Array.from(visited).join(" -> ")} -> ${key}`
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
if (depth >= this.config.maxDepth) {
|
|
1037
|
+
return {
|
|
1038
|
+
status: "unknown",
|
|
1039
|
+
modelId,
|
|
1040
|
+
reason: `Max pass-through depth (${this.config.maxDepth}) exceeded`
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
visited.add(key);
|
|
1044
|
+
const pricing = this.getDirectPricing(providerId, modelId);
|
|
1045
|
+
if (pricing) {
|
|
1046
|
+
return {
|
|
1047
|
+
status: "resolved",
|
|
1048
|
+
provider: providerId,
|
|
1049
|
+
model: modelId,
|
|
1050
|
+
pricing: pricing.pricing,
|
|
1051
|
+
source: pricing.source
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
const passthrough = this.getPassthroughTarget(providerId, modelId);
|
|
1055
|
+
if (passthrough) {
|
|
1056
|
+
const upstream = this.resolvePricing(
|
|
1057
|
+
passthrough.provider,
|
|
1058
|
+
passthrough.model,
|
|
1059
|
+
depth + 1,
|
|
1060
|
+
visited
|
|
1061
|
+
);
|
|
1062
|
+
return upstream.status === "resolved" ? { ...upstream, pricing: applyTargetToPricing(upstream.pricing, passthrough) } : upstream;
|
|
1063
|
+
}
|
|
1064
|
+
return {
|
|
1065
|
+
status: "unknown",
|
|
1066
|
+
modelId,
|
|
1067
|
+
reason: `No pricing found for ${providerId}:${modelId}`
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Get pass-through target for a model, if configured. Names the upstream row
|
|
1072
|
+
* without resolving its rates, for callers that read rows from their own
|
|
1073
|
+
* registry snapshot: such a caller passes that registry's `pricing` block so
|
|
1074
|
+
* the lookup neither reads nor clones the live registry per model.
|
|
1075
|
+
* `hasPricingRow` answers which rows exist for the same reason, defaulting to
|
|
1076
|
+
* the live registry.
|
|
1077
|
+
*/
|
|
1078
|
+
getPassthroughTarget(providerId, modelId, providerPricing, hasPricingRow = registryHasPricingRow) {
|
|
1079
|
+
const normalizedProvider = providerId.toLowerCase();
|
|
1080
|
+
const normalizedModel = modelId.toLowerCase();
|
|
1081
|
+
const customMap = this.customMappings.get(normalizedProvider);
|
|
1082
|
+
const customTarget = customMap?.[normalizedModel];
|
|
1083
|
+
if (customTarget) {
|
|
1084
|
+
return customTarget;
|
|
1085
|
+
}
|
|
1086
|
+
try {
|
|
1087
|
+
const pricingConfig = providerPricing ?? ProviderRegistry.getProviderConfig(normalizedProvider)?.pricing;
|
|
1088
|
+
if (pricingConfig && "_meta" in pricingConfig) {
|
|
1089
|
+
const meta = pricingConfig._meta;
|
|
1090
|
+
const yamlTarget = meta?.passthrough_map?.[normalizedModel];
|
|
1091
|
+
if (yamlTarget) {
|
|
1092
|
+
return yamlTarget;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
const providerTarget = this.resolveProviderPassthroughTarget(pricingConfig, normalizedModel);
|
|
1096
|
+
if (providerTarget) {
|
|
1097
|
+
return providerTarget;
|
|
1098
|
+
}
|
|
1099
|
+
if (pricingConfig && "passthrough_map" in pricingConfig) {
|
|
1100
|
+
const map = pricingConfig.passthrough_map;
|
|
1101
|
+
const directTarget = map[normalizedModel];
|
|
1102
|
+
if (directTarget) {
|
|
1103
|
+
return directTarget;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
} catch {
|
|
1107
|
+
}
|
|
1108
|
+
if (normalizedProvider === "cursor") {
|
|
1109
|
+
return DEFAULT_CURSOR_MAPPINGS[normalizedModel] ?? resolveCursorMakerPriceTarget(normalizedModel, hasPricingRow);
|
|
1110
|
+
}
|
|
1111
|
+
return null;
|
|
1112
|
+
}
|
|
1113
|
+
/** Provider-level route: the first-class `pricing.passthrough`, else the legacy `_meta` keys. */
|
|
1114
|
+
resolveProviderPassthroughTarget(pricingConfig, normalizedModel) {
|
|
1115
|
+
const meta = pricingConfig?._meta ?? {};
|
|
1116
|
+
const provider = pricingConfig?.passthrough?.provider ?? meta.passthrough_provider;
|
|
1117
|
+
if (!provider) {
|
|
1118
|
+
return null;
|
|
1119
|
+
}
|
|
1120
|
+
return {
|
|
1121
|
+
provider,
|
|
1122
|
+
model: stripConfiguredRoutePrefixes(
|
|
1123
|
+
normalizedModel,
|
|
1124
|
+
pricingConfig?.passthrough?.strip_prefixes ?? meta.passthrough_strip_prefixes
|
|
1125
|
+
)
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Get direct pricing for a model from the shared metadata resolver.
|
|
1130
|
+
*/
|
|
1131
|
+
getDirectPricing(providerId, modelId) {
|
|
1132
|
+
try {
|
|
1133
|
+
const resolvedPricing = ModelMetadataResolver.getPricing(providerId, modelId);
|
|
1134
|
+
if (!resolvedPricing) {
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
const { modelPricing: pricing } = resolvedPricing;
|
|
1138
|
+
const card = rateCardFromPricingRow(pricing);
|
|
1139
|
+
if (!card) {
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
return {
|
|
1143
|
+
pricing: {
|
|
1144
|
+
input_per_1m_tokens: formatUsdPer1m(card.inputPer1m),
|
|
1145
|
+
output_per_1m_tokens: formatUsdPer1m(card.outputPer1m),
|
|
1146
|
+
cache_read_per_1m_tokens: card.cacheReadPer1m === void 0 ? void 0 : formatUsdPer1m(card.cacheReadPer1m),
|
|
1147
|
+
cache_write_per_1m_tokens: card.cacheWritePer1m === void 0 ? void 0 : formatUsdPer1m(card.cacheWritePer1m),
|
|
1148
|
+
cache_write_1h_per_1m_tokens: card.cacheWriteLongPer1m === void 0 ? void 0 : formatUsdPer1m(card.cacheWriteLongPer1m),
|
|
1149
|
+
long_context_pricing: pricing.long_context_pricing,
|
|
1150
|
+
speed_tiers: pricing.speed_tiers,
|
|
1151
|
+
context_window: pricing.context_window,
|
|
1152
|
+
features: pricing.features,
|
|
1153
|
+
notes: pricing.notes
|
|
1154
|
+
},
|
|
1155
|
+
source: pricingSourceFromRow(pricing) ?? "manual"
|
|
1156
|
+
};
|
|
1157
|
+
} catch {
|
|
1158
|
+
return null;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Check if a provider uses pass-through pricing.
|
|
1163
|
+
*/
|
|
1164
|
+
isPassthroughProvider(providerId) {
|
|
1165
|
+
const normalizedProvider = providerId.toLowerCase();
|
|
1166
|
+
if (this.customMappings.has(normalizedProvider)) {
|
|
1167
|
+
return true;
|
|
1168
|
+
}
|
|
1169
|
+
try {
|
|
1170
|
+
const providerConfig = ProviderRegistry.getProviderConfig(normalizedProvider);
|
|
1171
|
+
const pricingConfig = providerConfig?.pricing;
|
|
1172
|
+
if (pricingConfig?.passthrough) {
|
|
1173
|
+
return true;
|
|
1174
|
+
}
|
|
1175
|
+
if (pricingConfig && "_meta" in pricingConfig) {
|
|
1176
|
+
const meta = pricingConfig._meta;
|
|
1177
|
+
return meta?.pricing_source === "passthrough";
|
|
1178
|
+
}
|
|
1179
|
+
} catch {
|
|
1180
|
+
}
|
|
1181
|
+
return normalizedProvider === "cursor";
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Get all pass-through mappings for a provider.
|
|
1185
|
+
*/
|
|
1186
|
+
getMappings(providerId) {
|
|
1187
|
+
const normalizedProvider = providerId.toLowerCase();
|
|
1188
|
+
const mappings = {};
|
|
1189
|
+
const customMap = this.customMappings.get(normalizedProvider);
|
|
1190
|
+
if (customMap) {
|
|
1191
|
+
Object.assign(mappings, customMap);
|
|
1192
|
+
}
|
|
1193
|
+
try {
|
|
1194
|
+
const providerConfig = ProviderRegistry.getProviderConfig(normalizedProvider);
|
|
1195
|
+
const pricingConfig = providerConfig?.pricing;
|
|
1196
|
+
if (pricingConfig && "_meta" in pricingConfig) {
|
|
1197
|
+
const meta = pricingConfig._meta;
|
|
1198
|
+
if (meta?.passthrough_map) {
|
|
1199
|
+
Object.assign(mappings, meta.passthrough_map);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (pricingConfig && "passthrough_map" in pricingConfig) {
|
|
1203
|
+
const map = pricingConfig.passthrough_map;
|
|
1204
|
+
Object.assign(mappings, map);
|
|
1205
|
+
}
|
|
1206
|
+
} catch {
|
|
1207
|
+
}
|
|
1208
|
+
if (normalizedProvider === "cursor") {
|
|
1209
|
+
return { ...DEFAULT_CURSOR_MAPPINGS, ...mappings };
|
|
1210
|
+
}
|
|
1211
|
+
return mappings;
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
function registryHasPricingRow(providerId, modelId) {
|
|
1215
|
+
try {
|
|
1216
|
+
return ProviderRegistry.getProviderConfig(providerId)?.pricing?.models?.[modelId] !== void 0;
|
|
1217
|
+
} catch {
|
|
1218
|
+
return false;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
function stripConfiguredRoutePrefixes(modelId, prefixes) {
|
|
1222
|
+
if (!prefixes || prefixes.length === 0) {
|
|
1223
|
+
return modelId;
|
|
1224
|
+
}
|
|
1225
|
+
const removablePrefixes = new Set(prefixes.map((prefix) => prefix.toLowerCase().trim()));
|
|
1226
|
+
const segments = modelId.split("/").filter(Boolean);
|
|
1227
|
+
while (segments.length > 2 && removablePrefixes.has(segments[0]?.toLowerCase() ?? "")) {
|
|
1228
|
+
segments.shift();
|
|
1229
|
+
}
|
|
1230
|
+
return segments.join("/") || modelId;
|
|
1231
|
+
}
|
|
1232
|
+
function formatUsdPer1m(value) {
|
|
1233
|
+
const [whole = "0", fraction = ""] = value.toFixed(6).split(".");
|
|
1234
|
+
return `${whole}.${fraction.replace(/0+$/, "").padEnd(2, "0")}`;
|
|
1235
|
+
}
|
|
1236
|
+
function joinPricingNotes(...notes) {
|
|
1237
|
+
const present = notes.filter((note) => Boolean(note));
|
|
1238
|
+
return present.length > 0 ? present.join(" ") : void 0;
|
|
1239
|
+
}
|
|
1240
|
+
function applyTargetToPricing(pricing, target) {
|
|
1241
|
+
const noted = target.note ? { ...pricing, notes: joinPricingNotes(pricing.notes, target.note) } : pricing;
|
|
1242
|
+
const tierName = target.speed_tier;
|
|
1243
|
+
if (!tierName) {
|
|
1244
|
+
return noted;
|
|
1245
|
+
}
|
|
1246
|
+
const tier = pricing.speed_tiers?.[tierName];
|
|
1247
|
+
if (!tier) {
|
|
1248
|
+
return {
|
|
1249
|
+
...noted,
|
|
1250
|
+
routed_speed_tier: { requested: tierName, applied: "standard" },
|
|
1251
|
+
notes: joinPricingNotes(noted.notes, `${tierName} requested, priced at standard.`)
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
const unpublishedAbove = tier.long_context_pricing ? void 0 : pricing.long_context_pricing?.threshold_tokens;
|
|
1255
|
+
return {
|
|
1256
|
+
...noted,
|
|
1257
|
+
input_per_1m_tokens: formatUsdPer1m(tier.input_per_1m_tokens),
|
|
1258
|
+
output_per_1m_tokens: formatUsdPer1m(tier.output_per_1m_tokens),
|
|
1259
|
+
cache_read_per_1m_tokens: tier.cache_read_per_1m_tokens === void 0 ? void 0 : formatUsdPer1m(tier.cache_read_per_1m_tokens),
|
|
1260
|
+
cache_write_per_1m_tokens: tier.cache_write_per_1m_tokens === void 0 ? void 0 : formatUsdPer1m(tier.cache_write_per_1m_tokens),
|
|
1261
|
+
cache_write_1h_per_1m_tokens: tier.cache_write_1h_per_1m_tokens === void 0 ? void 0 : formatUsdPer1m(tier.cache_write_1h_per_1m_tokens),
|
|
1262
|
+
long_context_pricing: tier.long_context_pricing,
|
|
1263
|
+
speed_tiers: void 0,
|
|
1264
|
+
routed_speed_tier: {
|
|
1265
|
+
requested: tierName,
|
|
1266
|
+
applied: tierName,
|
|
1267
|
+
...unpublishedAbove === void 0 ? {} : { unpublishedLongContextAboveTokens: unpublishedAbove }
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
var passthroughResolver = new PassthroughResolver();
|
|
1272
|
+
function resolvePassthroughPricing(providerId, modelId) {
|
|
1273
|
+
return passthroughResolver.resolvePricing(providerId, modelId);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// src/telemetry/worker.ts
|
|
1277
|
+
import { createHash as createHash2 } from "crypto";
|
|
1278
|
+
|
|
1279
|
+
// src/cost-tracking/cache-write-duration.ts
|
|
1280
|
+
var CACHE_WRITE_LONG_TOKENS_DIMENSION = "cacheWriteLongTokens";
|
|
1281
|
+
function clampLongCacheWriteTokens(cacheWriteTokens, longTokens) {
|
|
1282
|
+
if (longTokens === void 0 || !Number.isFinite(longTokens) || longTokens <= 0) return 0;
|
|
1283
|
+
return Math.min(longTokens, Math.max(0, cacheWriteTokens ?? 0));
|
|
1284
|
+
}
|
|
1285
|
+
function readLongCacheWriteTokens(extraDimensionsJson) {
|
|
1286
|
+
if (!extraDimensionsJson) return void 0;
|
|
1287
|
+
try {
|
|
1288
|
+
const value = JSON.parse(extraDimensionsJson)?.[CACHE_WRITE_LONG_TOKENS_DIMENSION];
|
|
1289
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
1290
|
+
} catch {
|
|
1291
|
+
return void 0;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
function cacheWriteUsd(writeRate, longRate, cacheWriteTokens, longTokens) {
|
|
1295
|
+
const long = clampLongCacheWriteTokens(cacheWriteTokens, longTokens);
|
|
1296
|
+
const effectiveLongRate = longRate ?? writeRate;
|
|
1297
|
+
return (writeRate === void 0 ? 0 : (cacheWriteTokens - long) / 1e6 * writeRate) + (effectiveLongRate === void 0 ? 0 : long / 1e6 * effectiveLongRate);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// src/cost-tracking/usage-granularity.ts
|
|
1301
|
+
function usageGranularityFromEventSemantics(eventSemantics) {
|
|
1302
|
+
return eventSemantics === "final" ? "request" : "run_aggregate";
|
|
1303
|
+
}
|
|
1304
|
+
function longContextContextTokens(usage) {
|
|
1305
|
+
const cacheReadTokens = usage.cacheReadIncludedInInput ? 0 : usage.cacheReadTokens ?? 0;
|
|
1306
|
+
const cacheWriteTokens = usage.cacheWriteIncludedInInput ? 0 : usage.cacheWriteTokens ?? 0;
|
|
1307
|
+
return usage.inputTokens + cacheReadTokens + cacheWriteTokens;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// src/cost-tracking/request-long-context.ts
|
|
1311
|
+
var MAX_REQUEST_EVIDENCE = 1e3;
|
|
1312
|
+
function boundRequestEvidence(requests, complete = true) {
|
|
1313
|
+
if (requests.length === 0) return void 0;
|
|
1314
|
+
const truncated = requests.length > MAX_REQUEST_EVIDENCE;
|
|
1315
|
+
return {
|
|
1316
|
+
requests: truncated ? requests.slice(0, MAX_REQUEST_EVIDENCE) : requests,
|
|
1317
|
+
complete: complete && !truncated
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
var STATUS_PRECEDENCE = ["applied", "below_threshold", "not_defined"];
|
|
1321
|
+
function sumOf(requests, pick) {
|
|
1322
|
+
return requests.reduce((total, request) => total + pick(request), 0);
|
|
1323
|
+
}
|
|
1324
|
+
function premiumIncrement(rates, usage, request) {
|
|
1325
|
+
const premium = rates.longContext;
|
|
1326
|
+
if (!premium) return { inputUsd: 0, outputUsd: 0, cacheUsd: 0 };
|
|
1327
|
+
const cacheRead = request.cachedInputTokens;
|
|
1328
|
+
const cacheWrite = request.cacheWriteTokens;
|
|
1329
|
+
const cacheWriteLong = clampLongCacheWriteTokens(cacheWrite, request.cacheWriteLongTokens);
|
|
1330
|
+
const uncached = Math.max(0, request.inputTokens - cacheRead - cacheWrite);
|
|
1331
|
+
const inputBilled = uncached + (rates.cacheReadPer1m === void 0 && usage.cacheReadIncludedInInput ? cacheRead : 0);
|
|
1332
|
+
const extra = (multiplier, rate, tokens) => rate === void 0 ? 0 : ((multiplier ?? 1) - 1) * rate * tokens / 1e6;
|
|
1333
|
+
return {
|
|
1334
|
+
inputUsd: extra(premium.inputMultiplier, rates.inputPer1m, inputBilled),
|
|
1335
|
+
outputUsd: extra(premium.outputMultiplier, rates.outputPer1m, request.outputTokens),
|
|
1336
|
+
cacheUsd: extra(premium.cacheReadMultiplier, rates.cacheReadPer1m, cacheRead) + extra(premium.cacheWriteMultiplier, rates.cacheWritePer1m, cacheWrite - cacheWriteLong) + extra(
|
|
1337
|
+
premium.cacheWriteMultiplier,
|
|
1338
|
+
rates.cacheWriteLongPer1m ?? rates.cacheWritePer1m,
|
|
1339
|
+
cacheWriteLong
|
|
1340
|
+
)
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
function assessRequestLongContext(standard, ratesFor, usage, evidence) {
|
|
1344
|
+
const requests = evidence?.requests ?? [];
|
|
1345
|
+
if (!evidence || requests.length === 0) return void 0;
|
|
1346
|
+
const runPrompt = longContextContextTokens(usage);
|
|
1347
|
+
const covered = [
|
|
1348
|
+
[sumOf(requests, (r) => r.inputTokens), runPrompt],
|
|
1349
|
+
[sumOf(requests, (r) => r.outputTokens), usage.outputTokens],
|
|
1350
|
+
[sumOf(requests, (r) => r.cachedInputTokens), usage.cacheReadTokens ?? 0],
|
|
1351
|
+
[sumOf(requests, (r) => r.cacheWriteTokens), usage.cacheWriteTokens ?? 0]
|
|
1352
|
+
];
|
|
1353
|
+
if (covered.some(([requested, run]) => requested > run)) {
|
|
1354
|
+
return {
|
|
1355
|
+
inputUsd: 0,
|
|
1356
|
+
outputUsd: 0,
|
|
1357
|
+
cacheUsd: 0,
|
|
1358
|
+
status: "not_assessed",
|
|
1359
|
+
incompleteReason: "request usage exceeds the run usage"
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
const reasons = [];
|
|
1363
|
+
if (!evidence.complete) reasons.push("request evidence is incomplete");
|
|
1364
|
+
if (covered.some(([requested, run]) => requested < run)) {
|
|
1365
|
+
reasons.push("request evidence covers only part of the run usage");
|
|
1366
|
+
}
|
|
1367
|
+
const total = { inputUsd: 0, outputUsd: 0, cacheUsd: 0 };
|
|
1368
|
+
const statuses = /* @__PURE__ */ new Set();
|
|
1369
|
+
const unpriceableTiers = /* @__PURE__ */ new Set();
|
|
1370
|
+
for (const request of requests) {
|
|
1371
|
+
const { rates, tier, unresolvedTier } = ratesFor(request.speed);
|
|
1372
|
+
if (unresolvedTier && standard.longContext && request.inputTokens > standard.longContext.thresholdTokens) {
|
|
1373
|
+
unpriceableTiers.add(unresolvedTier);
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1376
|
+
const premium = rates.longContext;
|
|
1377
|
+
if (!premium) {
|
|
1378
|
+
if (rates !== standard && standard.longContext && request.inputTokens > standard.longContext.thresholdTokens) {
|
|
1379
|
+
unpriceableTiers.add(tier);
|
|
1380
|
+
} else {
|
|
1381
|
+
statuses.add("not_defined");
|
|
1382
|
+
}
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
if (request.inputTokens > premium.thresholdTokens) {
|
|
1386
|
+
statuses.add("applied");
|
|
1387
|
+
const increment = premiumIncrement(rates, usage, request);
|
|
1388
|
+
total.inputUsd += increment.inputUsd;
|
|
1389
|
+
total.outputUsd += increment.outputUsd;
|
|
1390
|
+
total.cacheUsd += increment.cacheUsd;
|
|
1391
|
+
} else {
|
|
1392
|
+
statuses.add("below_threshold");
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
if (unpriceableTiers.size > 0) {
|
|
1396
|
+
reasons.push(`no long-context rates for speed tier ${[...unpriceableTiers].sort().join(", ")}`);
|
|
1397
|
+
}
|
|
1398
|
+
return {
|
|
1399
|
+
...total,
|
|
1400
|
+
status: reasons.length > 0 ? "not_assessed" : STATUS_PRECEDENCE.find((status) => statuses.has(status)) ?? "not_assessed",
|
|
1401
|
+
...reasons.length > 0 ? { incompleteReason: reasons.join("; ") } : {}
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// src/cost-tracking/list-price.ts
|
|
1406
|
+
function listPriceUsageFromRecord(record) {
|
|
1407
|
+
return {
|
|
1408
|
+
inputTokens: record.inputTokens,
|
|
1409
|
+
outputTokens: record.outputTokens,
|
|
1410
|
+
cacheReadTokens: record.cacheReadTokens ?? 0,
|
|
1411
|
+
cacheWriteTokens: record.cacheWriteTokens,
|
|
1412
|
+
cacheWriteLongTokens: record.cacheWriteLongTokens,
|
|
1413
|
+
cacheReadIncludedInInput: record.cacheReadTokensIncludedInInput,
|
|
1414
|
+
granularity: record.granularity,
|
|
1415
|
+
requestEvidence: record.requestEvidence
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
var STANDARD_SPEED_TIER = "standard";
|
|
1419
|
+
function normalizeTierName(tier) {
|
|
1420
|
+
const normalized = tier?.trim().toLowerCase();
|
|
1421
|
+
return normalized ? normalized : void 0;
|
|
1422
|
+
}
|
|
1423
|
+
function selectListPriceRates(card, requestedTier) {
|
|
1424
|
+
const requested = normalizeTierName(requestedTier);
|
|
1425
|
+
if (requested === void 0 || requested === STANDARD_SPEED_TIER) {
|
|
1426
|
+
return {
|
|
1427
|
+
rates: card,
|
|
1428
|
+
speedTier: { ...requested ? { requested } : {}, applied: STANDARD_SPEED_TIER }
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
const tierEntry = Object.entries(card.speedTiers ?? {}).find(
|
|
1432
|
+
([name]) => name.toLowerCase() === requested
|
|
1433
|
+
);
|
|
1434
|
+
return tierEntry ? { rates: tierEntry[1], speedTier: { requested, applied: requested } } : { rates: card, speedTier: { requested, applied: STANDARD_SPEED_TIER } };
|
|
1435
|
+
}
|
|
1436
|
+
function routedSpeedTier(runRequested, routed) {
|
|
1437
|
+
if (normalizeTierName(runRequested) !== void 0 || !routed) {
|
|
1438
|
+
return { requested: runRequested };
|
|
1439
|
+
}
|
|
1440
|
+
if (routed.applied === STANDARD_SPEED_TIER) {
|
|
1441
|
+
return { requested: routed.requested };
|
|
1442
|
+
}
|
|
1443
|
+
return {
|
|
1444
|
+
reported: { requested: routed.requested, applied: routed.applied },
|
|
1445
|
+
unpublishedAbove: routed.unpublishedLongContextAboveTokens
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
function unpublishedThreshold(card, rates, routedAbove) {
|
|
1449
|
+
return routedAbove ?? (rates === card ? void 0 : card.longContext?.thresholdTokens);
|
|
1450
|
+
}
|
|
1451
|
+
function priceWithRates(rates, usage, unpublishedAbove) {
|
|
1452
|
+
const cacheReadTokens = usage.cacheReadTokens ?? 0;
|
|
1453
|
+
const cacheWriteTokens = usage.cacheWriteTokens ?? 0;
|
|
1454
|
+
const cacheWriteLongTokens = clampLongCacheWriteTokens(
|
|
1455
|
+
cacheWriteTokens,
|
|
1456
|
+
usage.cacheWriteLongTokens
|
|
1457
|
+
);
|
|
1458
|
+
const longContext = rates.longContext;
|
|
1459
|
+
let longContextPremium;
|
|
1460
|
+
let premium;
|
|
1461
|
+
const standardThreshold = unpublishedAbove;
|
|
1462
|
+
if (usage.granularity !== "request") {
|
|
1463
|
+
const threshold = longContext?.thresholdTokens ?? standardThreshold;
|
|
1464
|
+
longContextPremium = threshold !== void 0 && longContextContextTokens(usage) <= threshold ? "below_threshold" : "not_assessed";
|
|
1465
|
+
} else if (!longContext) {
|
|
1466
|
+
longContextPremium = standardThreshold === void 0 ? "not_defined" : longContextContextTokens(usage) > standardThreshold ? "not_assessed" : "below_threshold";
|
|
1467
|
+
} else if (longContextContextTokens(usage) > longContext.thresholdTokens) {
|
|
1468
|
+
longContextPremium = "applied";
|
|
1469
|
+
premium = longContext;
|
|
1470
|
+
} else {
|
|
1471
|
+
longContextPremium = "below_threshold";
|
|
1472
|
+
}
|
|
1473
|
+
const inputRate = rates.inputPer1m * (premium?.inputMultiplier ?? 1);
|
|
1474
|
+
const outputRate = rates.outputPer1m * (premium?.outputMultiplier ?? 1);
|
|
1475
|
+
const cacheReadRate = rates.cacheReadPer1m === void 0 ? void 0 : rates.cacheReadPer1m * (premium?.cacheReadMultiplier ?? 1);
|
|
1476
|
+
const cacheWriteRate = rates.cacheWritePer1m === void 0 ? void 0 : rates.cacheWritePer1m * (premium?.cacheWriteMultiplier ?? 1);
|
|
1477
|
+
const cacheWriteLongRate = rates.cacheWriteLongPer1m === void 0 ? void 0 : rates.cacheWriteLongPer1m * (premium?.cacheWriteMultiplier ?? 1);
|
|
1478
|
+
const billableInputTokens = usage.cacheReadIncludedInInput && cacheReadRate !== void 0 ? Math.max(0, usage.inputTokens - cacheReadTokens) : usage.inputTokens;
|
|
1479
|
+
return {
|
|
1480
|
+
inputUsd: billableInputTokens / 1e6 * inputRate,
|
|
1481
|
+
outputUsd: usage.outputTokens / 1e6 * outputRate,
|
|
1482
|
+
cacheUsd: (cacheReadRate === void 0 ? 0 : cacheReadTokens / 1e6 * cacheReadRate) + cacheWriteUsd(cacheWriteRate, cacheWriteLongRate, cacheWriteTokens, cacheWriteLongTokens),
|
|
1483
|
+
longContextPremium
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
var PREMIUM_PRECEDENCE = [
|
|
1487
|
+
"applied",
|
|
1488
|
+
"below_threshold",
|
|
1489
|
+
"not_assessed",
|
|
1490
|
+
"not_defined"
|
|
1491
|
+
];
|
|
1492
|
+
function speedPortionUsage(usage, portion) {
|
|
1493
|
+
return {
|
|
1494
|
+
...usage,
|
|
1495
|
+
inputTokens: portion.inputTokens,
|
|
1496
|
+
outputTokens: portion.outputTokens,
|
|
1497
|
+
cacheReadTokens: portion.cacheReadTokens ?? 0,
|
|
1498
|
+
cacheWriteTokens: portion.cacheWriteTokens ?? 0,
|
|
1499
|
+
// The run's long-write count describes the whole run, not this portion.
|
|
1500
|
+
cacheWriteLongTokens: portion.cacheWriteLongTokens ?? 0
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
function breakdownMatchesUsage(usage, portions) {
|
|
1504
|
+
const sum = (field) => portions.reduce((total, portion) => total + (portion[field] ?? 0), 0);
|
|
1505
|
+
return sum("inputTokens") === usage.inputTokens && sum("outputTokens") === usage.outputTokens && sum("cacheReadTokens") === (usage.cacheReadTokens ?? 0) && sum("cacheWriteTokens") === (usage.cacheWriteTokens ?? 0);
|
|
1506
|
+
}
|
|
1507
|
+
function computeSpeedBreakdownPrice(card, usage, requestedTier, breakdown, routedAbove) {
|
|
1508
|
+
const requested = normalizeTierName(requestedTier);
|
|
1509
|
+
const tierEntries = Object.entries(breakdown.tiers).map(([name, portion]) => [normalizeTierName(name), portion]).filter((entry) => entry[0] !== void 0).sort(([a], [b]) => a.localeCompare(b));
|
|
1510
|
+
if (tierEntries.length === 0 && !breakdown.unknown) return void 0;
|
|
1511
|
+
const observed = tierEntries.map(([name]) => name);
|
|
1512
|
+
const portions = [...tierEntries.map(([, portion]) => portion), breakdown.unknown].filter(
|
|
1513
|
+
(portion) => portion !== void 0
|
|
1514
|
+
);
|
|
1515
|
+
const unknownIsStandard = breakdown.unknown !== void 0 && Object.keys(card.speedTiers ?? {}).length === 0;
|
|
1516
|
+
const effectiveTiers = /* @__PURE__ */ new Set([
|
|
1517
|
+
...observed,
|
|
1518
|
+
...unknownIsStandard ? [STANDARD_SPEED_TIER] : []
|
|
1519
|
+
]);
|
|
1520
|
+
const [onlyTier] = effectiveTiers;
|
|
1521
|
+
if (effectiveTiers.size === 1 && onlyTier !== void 0 && (!breakdown.unknown || unknownIsStandard)) {
|
|
1522
|
+
const { rates, speedTier } = selectListPriceRates(card, onlyTier);
|
|
1523
|
+
if (speedTier.applied === onlyTier) {
|
|
1524
|
+
const priced2 = priceWithRates(rates, usage, unpublishedThreshold(card, rates, routedAbove));
|
|
1525
|
+
return {
|
|
1526
|
+
...priced2,
|
|
1527
|
+
totalUsd: priced2.inputUsd + priced2.outputUsd + priced2.cacheUsd,
|
|
1528
|
+
speedTier: {
|
|
1529
|
+
...requested ? { requested } : {},
|
|
1530
|
+
applied: onlyTier,
|
|
1531
|
+
observed,
|
|
1532
|
+
assessment: "complete"
|
|
1533
|
+
},
|
|
1534
|
+
complete: true
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
const missingTiers = observed.filter(
|
|
1539
|
+
(name) => selectListPriceRates(card, name).speedTier.applied !== name
|
|
1540
|
+
);
|
|
1541
|
+
const incompleteReason = !breakdownMatchesUsage(usage, portions) ? "speed breakdown does not sum to the run usage" : missingTiers.length > 0 ? `no rates for observed speed tier ${missingTiers.join(", ")}` : void 0;
|
|
1542
|
+
if (incompleteReason !== void 0) {
|
|
1543
|
+
const fallback = computeBaseListPrice(card, usage, requestedTier, void 0, routedAbove);
|
|
1544
|
+
return {
|
|
1545
|
+
...fallback,
|
|
1546
|
+
speedTier: { ...fallback.speedTier, observed, assessment: "incomplete", incompleteReason }
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
const priced = [];
|
|
1550
|
+
const appliedNames = /* @__PURE__ */ new Set();
|
|
1551
|
+
for (const [name, portion] of tierEntries) {
|
|
1552
|
+
const { rates, speedTier } = selectListPriceRates(card, name);
|
|
1553
|
+
appliedNames.add(speedTier.applied);
|
|
1554
|
+
priced.push(
|
|
1555
|
+
priceWithRates(
|
|
1556
|
+
rates,
|
|
1557
|
+
speedPortionUsage(usage, portion),
|
|
1558
|
+
unpublishedThreshold(card, rates, routedAbove)
|
|
1559
|
+
)
|
|
1560
|
+
);
|
|
1561
|
+
}
|
|
1562
|
+
if (breakdown.unknown) {
|
|
1563
|
+
const { rates, speedTier } = selectListPriceRates(card, requested);
|
|
1564
|
+
appliedNames.add(speedTier.applied);
|
|
1565
|
+
priced.push(
|
|
1566
|
+
priceWithRates(
|
|
1567
|
+
rates,
|
|
1568
|
+
speedPortionUsage(usage, breakdown.unknown),
|
|
1569
|
+
unpublishedThreshold(card, rates, routedAbove)
|
|
1570
|
+
)
|
|
1571
|
+
);
|
|
1572
|
+
}
|
|
1573
|
+
const inputUsd = priced.reduce((total, portion) => total + portion.inputUsd, 0);
|
|
1574
|
+
const outputUsd = priced.reduce((total, portion) => total + portion.outputUsd, 0);
|
|
1575
|
+
const cacheUsd = priced.reduce((total, portion) => total + portion.cacheUsd, 0);
|
|
1576
|
+
const longContextPremium = PREMIUM_PRECEDENCE.find(
|
|
1577
|
+
(status) => priced.some((portion) => portion.longContextPremium === status)
|
|
1578
|
+
) ?? "not_assessed";
|
|
1579
|
+
const unknownSpeed = breakdown.unknown !== void 0;
|
|
1580
|
+
return {
|
|
1581
|
+
inputUsd,
|
|
1582
|
+
outputUsd,
|
|
1583
|
+
cacheUsd,
|
|
1584
|
+
totalUsd: inputUsd + outputUsd + cacheUsd,
|
|
1585
|
+
speedTier: {
|
|
1586
|
+
...requested ? { requested } : {},
|
|
1587
|
+
applied: appliedNames.size === 1 ? [...appliedNames].join("") : "mixed",
|
|
1588
|
+
observed,
|
|
1589
|
+
assessment: unknownSpeed ? "incomplete" : "complete",
|
|
1590
|
+
...unknownSpeed ? { incompleteReason: "some responses reported no speed" } : {}
|
|
1591
|
+
},
|
|
1592
|
+
longContextPremium,
|
|
1593
|
+
complete: true
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
function computeBaseListPrice(card, usage, requestedTier, speedBreakdown, routedAbove) {
|
|
1597
|
+
if (speedBreakdown) {
|
|
1598
|
+
const byBreakdown = computeSpeedBreakdownPrice(
|
|
1599
|
+
card,
|
|
1600
|
+
usage,
|
|
1601
|
+
requestedTier,
|
|
1602
|
+
speedBreakdown,
|
|
1603
|
+
routedAbove
|
|
1604
|
+
);
|
|
1605
|
+
if (byBreakdown) return byBreakdown;
|
|
1606
|
+
}
|
|
1607
|
+
const { rates, speedTier } = selectListPriceRates(card, requestedTier);
|
|
1608
|
+
const priced = priceWithRates(rates, usage, unpublishedThreshold(card, rates, routedAbove));
|
|
1609
|
+
const unverified = speedTier.applied !== STANDARD_SPEED_TIER;
|
|
1610
|
+
return {
|
|
1611
|
+
...priced,
|
|
1612
|
+
totalUsd: priced.inputUsd + priced.outputUsd + priced.cacheUsd,
|
|
1613
|
+
speedTier: unverified ? { ...speedTier, assessment: "incomplete", incompleteReason: "served tier unverified" } : speedTier,
|
|
1614
|
+
complete: true
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
function definesLongContext(card, routedAbove) {
|
|
1618
|
+
return routedAbove !== void 0 || card.longContext !== void 0 || Object.values(card.speedTiers ?? {}).some((tier) => tier.longContext !== void 0);
|
|
1619
|
+
}
|
|
1620
|
+
function isComplete(speedTier, premium, premiumDefined) {
|
|
1621
|
+
const tierFellBack = speedTier.requested !== void 0 && speedTier.requested !== STANDARD_SPEED_TIER && speedTier.requested !== speedTier.applied && // Evidence of the speed that ran prices the tier it observed, not a fallback.
|
|
1622
|
+
speedTier.assessment !== "complete";
|
|
1623
|
+
const speedUnassessed = speedTier.assessment === "incomplete";
|
|
1624
|
+
return !tierFellBack && !speedUnassessed && !(premium === "not_assessed" && premiumDefined);
|
|
1625
|
+
}
|
|
1626
|
+
function lowestLongContextThreshold(card, routedAbove) {
|
|
1627
|
+
const thresholds = [card, ...Object.values(card.speedTiers ?? {})].map((rates) => rates.longContext?.thresholdTokens).concat(routedAbove).filter((threshold) => threshold !== void 0);
|
|
1628
|
+
return thresholds.length > 0 ? Math.min(...thresholds) : void 0;
|
|
1629
|
+
}
|
|
1630
|
+
function computeListPrice(card, usage, requestedTier, options = {}) {
|
|
1631
|
+
const routedTier = routedSpeedTier(requestedTier, options.routed);
|
|
1632
|
+
const effectiveTier = routedTier.requested;
|
|
1633
|
+
const base = computeBaseListPrice(
|
|
1634
|
+
card,
|
|
1635
|
+
usage,
|
|
1636
|
+
effectiveTier,
|
|
1637
|
+
options.speedBreakdown,
|
|
1638
|
+
routedTier.unpublishedAbove
|
|
1639
|
+
);
|
|
1640
|
+
const premiumDefined = definesLongContext(card, routedTier.unpublishedAbove);
|
|
1641
|
+
const finish = (result) => {
|
|
1642
|
+
const speedTier = routedTier.reported ?? result.speedTier;
|
|
1643
|
+
return {
|
|
1644
|
+
...result,
|
|
1645
|
+
speedTier,
|
|
1646
|
+
complete: isComplete(speedTier, result.longContextPremium, premiumDefined)
|
|
1647
|
+
};
|
|
1648
|
+
};
|
|
1649
|
+
if (usage.granularity === "request") return finish(base);
|
|
1650
|
+
const lowestThreshold = lowestLongContextThreshold(card, routedTier.unpublishedAbove);
|
|
1651
|
+
const runPrompt = longContextContextTokens(usage);
|
|
1652
|
+
const contradicted = (usage.requestEvidence?.requests ?? []).some(
|
|
1653
|
+
(request) => request.inputTokens > runPrompt
|
|
1654
|
+
);
|
|
1655
|
+
if (lowestThreshold !== void 0 && runPrompt <= lowestThreshold && !contradicted) {
|
|
1656
|
+
return finish({ ...base, longContextPremium: "below_threshold" });
|
|
1657
|
+
}
|
|
1658
|
+
const assessed = assessRequestLongContext(
|
|
1659
|
+
card,
|
|
1660
|
+
(speed) => {
|
|
1661
|
+
const { rates, speedTier } = selectListPriceRates(card, speed ?? effectiveTier);
|
|
1662
|
+
const reported = normalizeTierName(speed);
|
|
1663
|
+
return {
|
|
1664
|
+
rates,
|
|
1665
|
+
tier: speedTier.applied,
|
|
1666
|
+
...reported && reported !== speedTier.applied ? { unresolvedTier: reported } : {}
|
|
1667
|
+
};
|
|
1668
|
+
},
|
|
1669
|
+
usage,
|
|
1670
|
+
usage.requestEvidence
|
|
1671
|
+
);
|
|
1672
|
+
if (!assessed) return finish(base);
|
|
1673
|
+
const routedAbove = routedTier.unpublishedAbove;
|
|
1674
|
+
const crossesUnpublished = routedAbove !== void 0 && (usage.requestEvidence?.requests ?? []).some((request) => request.inputTokens > routedAbove);
|
|
1675
|
+
const premium = crossesUnpublished && assessed.status !== "applied" ? {
|
|
1676
|
+
...assessed,
|
|
1677
|
+
status: "not_assessed",
|
|
1678
|
+
incompleteReason: [
|
|
1679
|
+
assessed.incompleteReason,
|
|
1680
|
+
"no long-context rates for the routed speed tier"
|
|
1681
|
+
].filter(Boolean).join("; ")
|
|
1682
|
+
} : assessed;
|
|
1683
|
+
const inputUsd = base.inputUsd + premium.inputUsd;
|
|
1684
|
+
const outputUsd = base.outputUsd + premium.outputUsd;
|
|
1685
|
+
const cacheUsd = base.cacheUsd + premium.cacheUsd;
|
|
1686
|
+
return finish({
|
|
1687
|
+
...base,
|
|
1688
|
+
inputUsd,
|
|
1689
|
+
outputUsd,
|
|
1690
|
+
cacheUsd,
|
|
1691
|
+
totalUsd: inputUsd + outputUsd + cacheUsd,
|
|
1692
|
+
longContextPremium: premium.status,
|
|
1693
|
+
...premium.incompleteReason ? { longContextIncompleteReason: premium.incompleteReason } : {}
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// src/telemetry/model-evidence.ts
|
|
1698
|
+
var EVIDENCE_FIELDS = ["source", "unverifiedReason", "modelProvider", "observedModelId"];
|
|
1699
|
+
var MAX_EVIDENCE_FIELD_LENGTH = 128;
|
|
1700
|
+
function cleanField(value) {
|
|
1701
|
+
if (typeof value !== "string") return void 0;
|
|
1702
|
+
const trimmed = value.trim();
|
|
1703
|
+
return trimmed.length > 0 && trimmed.length <= MAX_EVIDENCE_FIELD_LENGTH ? trimmed : void 0;
|
|
1704
|
+
}
|
|
1705
|
+
function cleanModelEvidence(evidence) {
|
|
1706
|
+
if (!evidence) return void 0;
|
|
1707
|
+
const cleaned = {};
|
|
1708
|
+
for (const field of EVIDENCE_FIELDS) {
|
|
1709
|
+
const value = cleanField(evidence[field]);
|
|
1710
|
+
if (value) cleaned[field] = value;
|
|
1711
|
+
}
|
|
1712
|
+
return Object.keys(cleaned).length > 0 ? cleaned : void 0;
|
|
1713
|
+
}
|
|
1714
|
+
function modelEvidenceAvailability(evidence) {
|
|
1715
|
+
const cleaned = cleanModelEvidence(evidence);
|
|
1716
|
+
return cleaned ? { modelEvidence: cleaned } : {};
|
|
1717
|
+
}
|
|
1718
|
+
function readModelEvidence(availabilityJson) {
|
|
1719
|
+
if (!availabilityJson) return void 0;
|
|
1720
|
+
try {
|
|
1721
|
+
const parsed = JSON.parse(availabilityJson);
|
|
1722
|
+
const stored = parsed?.modelEvidence;
|
|
1723
|
+
return stored && typeof stored === "object" ? cleanModelEvidence(stored) : void 0;
|
|
1724
|
+
} catch {
|
|
1725
|
+
return void 0;
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
function isAmbiguousModelEvidence(evidence) {
|
|
1729
|
+
return evidence?.unverifiedReason?.endsWith("_ambiguous") === true;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
// src/telemetry/observed-speed-usage.ts
|
|
1733
|
+
function observedSpeedAvailability(breakdown) {
|
|
1734
|
+
return breakdown ? { observedSpeed: breakdown } : {};
|
|
1735
|
+
}
|
|
1736
|
+
function readSpeedUsage(value) {
|
|
1737
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
1738
|
+
const record = value;
|
|
1739
|
+
const count = (key) => {
|
|
1740
|
+
const value2 = record[key];
|
|
1741
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? value2 : void 0;
|
|
1742
|
+
};
|
|
1743
|
+
const inputTokens = count("inputTokens");
|
|
1744
|
+
const outputTokens = count("outputTokens");
|
|
1745
|
+
if (inputTokens === void 0 || outputTokens === void 0) return void 0;
|
|
1746
|
+
return {
|
|
1747
|
+
inputTokens,
|
|
1748
|
+
outputTokens,
|
|
1749
|
+
cacheReadTokens: count("cacheReadTokens"),
|
|
1750
|
+
cacheWriteTokens: count("cacheWriteTokens"),
|
|
1751
|
+
cacheWriteLongTokens: count("cacheWriteLongTokens")
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
function readObservedSpeedBreakdown(availabilityJson) {
|
|
1755
|
+
if (!availabilityJson) return void 0;
|
|
1756
|
+
try {
|
|
1757
|
+
const parsed = JSON.parse(availabilityJson);
|
|
1758
|
+
const observed = parsed?.observedSpeed;
|
|
1759
|
+
if (typeof observed !== "object" || observed === null) return void 0;
|
|
1760
|
+
const tiers = {};
|
|
1761
|
+
for (const [name, value] of Object.entries(
|
|
1762
|
+
typeof observed.tiers === "object" && observed.tiers !== null ? observed.tiers : {}
|
|
1763
|
+
)) {
|
|
1764
|
+
const usage = readSpeedUsage(value);
|
|
1765
|
+
if (!usage) return void 0;
|
|
1766
|
+
tiers[name] = usage;
|
|
1767
|
+
}
|
|
1768
|
+
const unknown = observed.unknown === void 0 ? void 0 : readSpeedUsage(observed.unknown);
|
|
1769
|
+
if (observed.unknown !== void 0 && !unknown) return void 0;
|
|
1770
|
+
return { tiers, ...unknown ? { unknown } : {} };
|
|
1771
|
+
} catch {
|
|
1772
|
+
return void 0;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
// src/telemetry/request-evidence-usage.ts
|
|
1777
|
+
function requestEvidenceAvailability(evidence) {
|
|
1778
|
+
return evidence && evidence.requests.length > 0 ? { requestEvidence: evidence } : {};
|
|
1779
|
+
}
|
|
1780
|
+
function readRequest(value) {
|
|
1781
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
1782
|
+
const record = value;
|
|
1783
|
+
const count = (key) => {
|
|
1784
|
+
const field = record[key];
|
|
1785
|
+
return typeof field === "number" && Number.isFinite(field) && field >= 0 ? field : void 0;
|
|
1786
|
+
};
|
|
1787
|
+
const inputTokens = count("inputTokens");
|
|
1788
|
+
const cachedInputTokens = count("cachedInputTokens");
|
|
1789
|
+
const cacheWriteTokens = count("cacheWriteTokens");
|
|
1790
|
+
const outputTokens = count("outputTokens");
|
|
1791
|
+
const cacheWriteLongTokens = count("cacheWriteLongTokens");
|
|
1792
|
+
if (inputTokens === void 0 || cachedInputTokens === void 0 || cacheWriteTokens === void 0 || outputTokens === void 0) {
|
|
1793
|
+
return void 0;
|
|
1794
|
+
}
|
|
1795
|
+
return {
|
|
1796
|
+
inputTokens,
|
|
1797
|
+
cachedInputTokens,
|
|
1798
|
+
cacheWriteTokens,
|
|
1799
|
+
...cacheWriteLongTokens !== void 0 ? { cacheWriteLongTokens } : {},
|
|
1800
|
+
outputTokens,
|
|
1801
|
+
...typeof record.speed === "string" && record.speed ? { speed: record.speed } : {}
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
function parseRequestEvidence(value) {
|
|
1805
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
1806
|
+
const { requests, complete } = value;
|
|
1807
|
+
if (!Array.isArray(requests) || requests.length === 0 || typeof complete !== "boolean") {
|
|
1808
|
+
return void 0;
|
|
1809
|
+
}
|
|
1810
|
+
const parsed = [];
|
|
1811
|
+
for (const entry of requests) {
|
|
1812
|
+
const request = readRequest(entry);
|
|
1813
|
+
if (!request) return void 0;
|
|
1814
|
+
parsed.push(request);
|
|
1815
|
+
}
|
|
1816
|
+
return { requests: parsed, complete };
|
|
1817
|
+
}
|
|
1818
|
+
function readRequestEvidence(availabilityJson) {
|
|
1819
|
+
if (!availabilityJson) return void 0;
|
|
1820
|
+
try {
|
|
1821
|
+
const parsed = JSON.parse(availabilityJson);
|
|
1822
|
+
return parseRequestEvidence(parsed?.requestEvidence);
|
|
1823
|
+
} catch {
|
|
1824
|
+
return void 0;
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
// src/telemetry/requested-service-tier.ts
|
|
1829
|
+
function requestedServiceTierAvailability(serviceTier) {
|
|
1830
|
+
const tier = serviceTier?.trim();
|
|
1831
|
+
return tier ? { requested: { serviceTier: tier } } : {};
|
|
1832
|
+
}
|
|
1833
|
+
function readRequestedServiceTier(availabilityJson) {
|
|
1834
|
+
if (!availabilityJson) return void 0;
|
|
1835
|
+
try {
|
|
1836
|
+
const parsed = JSON.parse(availabilityJson);
|
|
1837
|
+
const tier = parsed?.requested?.serviceTier;
|
|
1838
|
+
return typeof tier === "string" && tier.trim() ? tier.trim() : void 0;
|
|
1839
|
+
} catch {
|
|
1840
|
+
return void 0;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
// src/telemetry/worker.ts
|
|
1845
|
+
function resolveRegistryModelCandidates(provider, modelId) {
|
|
1846
|
+
const trimmed = modelId.trim();
|
|
1847
|
+
if (trimmed.length === 0) {
|
|
1848
|
+
return [];
|
|
1849
|
+
}
|
|
1850
|
+
const candidates = /* @__PURE__ */ new Set([trimmed]);
|
|
1851
|
+
const mappings = provider.modelMappings ?? {};
|
|
1852
|
+
const directMapped = mappings[trimmed];
|
|
1853
|
+
if (directMapped) {
|
|
1854
|
+
candidates.add(directMapped);
|
|
1855
|
+
} else {
|
|
1856
|
+
const lowered = trimmed.toLowerCase();
|
|
1857
|
+
for (const [alias, target] of Object.entries(mappings)) {
|
|
1858
|
+
if (alias.toLowerCase() === lowered && target) {
|
|
1859
|
+
candidates.add(target);
|
|
1860
|
+
break;
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
return Array.from(candidates);
|
|
1865
|
+
}
|
|
1866
|
+
function roundCostCents(value) {
|
|
1867
|
+
return Math.round(value * 1e6) / 1e6;
|
|
1868
|
+
}
|
|
1869
|
+
function findRegistryModel(provider, modelId) {
|
|
1870
|
+
const candidates = resolveRegistryModelCandidates(provider, modelId);
|
|
1871
|
+
for (const candidate of candidates) {
|
|
1872
|
+
const model = provider.models.find((entry) => entry.modelId === candidate);
|
|
1873
|
+
if (model) {
|
|
1874
|
+
return { model, resolvedModelId: candidate };
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
return null;
|
|
1878
|
+
}
|
|
1879
|
+
function unverifiedModelSuffix(evidence) {
|
|
1880
|
+
return evidence?.unverifiedReason ? ` (model_unverified: ${evidence.unverifiedReason})` : "";
|
|
1881
|
+
}
|
|
1882
|
+
function optionalAttributionId(value) {
|
|
1883
|
+
if (typeof value !== "string") return null;
|
|
1884
|
+
const trimmed = value.trim();
|
|
1885
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1886
|
+
}
|
|
1887
|
+
function hasPricedTokenEvidence(invocation, evidence) {
|
|
1888
|
+
return [
|
|
1889
|
+
evidence?.inputTokens,
|
|
1890
|
+
evidence?.outputTokens,
|
|
1891
|
+
evidence?.cacheReadTokens,
|
|
1892
|
+
evidence?.cacheWriteTokens,
|
|
1893
|
+
invocation.tokensIn,
|
|
1894
|
+
invocation.tokensOut
|
|
1895
|
+
].some((value) => typeof value === "number");
|
|
1896
|
+
}
|
|
1897
|
+
function declaresMeteredRates(model) {
|
|
1898
|
+
const schema = model.costSchema;
|
|
1899
|
+
if (!schema) return false;
|
|
1900
|
+
return typeof schema.prompt_per_1k === "number" || typeof schema.completion_per_1k === "number" || typeof schema.cache_read_per_1k === "number" || typeof schema.cache_write_per_1k === "number" || schema.extra_fees !== void 0 || typeof schema.billing_source === "string";
|
|
1901
|
+
}
|
|
1902
|
+
function findProviderAndModel(registry, providerId, modelId) {
|
|
1903
|
+
const provider = registry.providers.find((p) => p.providerId === providerId);
|
|
1904
|
+
if (!provider) return null;
|
|
1905
|
+
const resolvedModel = findRegistryModel(provider, modelId);
|
|
1906
|
+
if (!resolvedModel) return null;
|
|
1907
|
+
return { provider, ...resolvedModel };
|
|
1908
|
+
}
|
|
1909
|
+
function collectRegistrySnapshotsForEnrichment(db, preferredRegistryVersion) {
|
|
1910
|
+
const preferred = db.getRegistrySnapshot(preferredRegistryVersion);
|
|
1911
|
+
return preferred ? [preferred] : [];
|
|
1912
|
+
}
|
|
1913
|
+
var TelemetryWorker = class {
|
|
1914
|
+
constructor(db, options = {}) {
|
|
1915
|
+
this.db = db;
|
|
1916
|
+
this.options = options;
|
|
1917
|
+
}
|
|
1918
|
+
db;
|
|
1919
|
+
options;
|
|
1920
|
+
warn(message) {
|
|
1921
|
+
if (this.options.diagnostics === "silent") return;
|
|
1922
|
+
console.warn(message);
|
|
1923
|
+
}
|
|
1924
|
+
error(message, detail) {
|
|
1925
|
+
if (this.options.diagnostics === "silent") return;
|
|
1926
|
+
console.error(message, detail);
|
|
1927
|
+
}
|
|
1928
|
+
markUnpriced(invocation, reason) {
|
|
1929
|
+
const enrichedAt = Date.now();
|
|
1930
|
+
this.db.insertEnrichedInvocation({
|
|
1931
|
+
id: generateInvocationId(),
|
|
1932
|
+
invocationId: invocation.id,
|
|
1933
|
+
computedCostCents: null,
|
|
1934
|
+
pricingSource: "unavailable",
|
|
1935
|
+
enrichedAt,
|
|
1936
|
+
billingMode: null,
|
|
1937
|
+
usageData: JSON.stringify({
|
|
1938
|
+
inputTokens: invocation.tokensIn ?? 0,
|
|
1939
|
+
outputTokens: invocation.tokensOut ?? 0
|
|
1940
|
+
}),
|
|
1941
|
+
costSnapshot: null,
|
|
1942
|
+
pricingVersion: invocation.registryVersion,
|
|
1943
|
+
taskId: optionalAttributionId(invocation.taskId),
|
|
1944
|
+
competitionId: optionalAttributionId(invocation.competitionId) ?? optionalAttributionId(invocation.taskId),
|
|
1945
|
+
variantId: optionalAttributionId(invocation.variantId)
|
|
1946
|
+
});
|
|
1947
|
+
const identity = JSON.stringify({
|
|
1948
|
+
invocationId: invocation.id,
|
|
1949
|
+
pricingVersion: invocation.registryVersion,
|
|
1950
|
+
methodologyVersion: "api-equivalent-v1",
|
|
1951
|
+
status: "unavailable"
|
|
1952
|
+
});
|
|
1953
|
+
this.db.insertCostEvaluation({
|
|
1954
|
+
evaluationId: createHash2("sha256").update(identity).digest("hex"),
|
|
1955
|
+
invocationId: invocation.id,
|
|
1956
|
+
pricingVersion: invocation.registryVersion,
|
|
1957
|
+
methodologyVersion: "api-equivalent-v1",
|
|
1958
|
+
apiEquivalentCostUsd: null,
|
|
1959
|
+
providerReportedCostUsd: invocation.providerReportedCostUsd ?? null,
|
|
1960
|
+
comparisonDeltaUsd: null,
|
|
1961
|
+
currency: "USD",
|
|
1962
|
+
status: "unavailable",
|
|
1963
|
+
createdAt: enrichedAt
|
|
1964
|
+
});
|
|
1965
|
+
this.warn(reason);
|
|
1966
|
+
}
|
|
1967
|
+
/**
|
|
1968
|
+
* Enrich unenriched invocations with cost data
|
|
1969
|
+
* Returns number of rows enriched
|
|
1970
|
+
*/
|
|
1971
|
+
async enrichInvocations(overrides) {
|
|
1972
|
+
const unenriched = this.db.getUnenrichedInvocations(1e3);
|
|
1973
|
+
let enrichedCount = 0;
|
|
1974
|
+
for (const invocation of unenriched) {
|
|
1975
|
+
try {
|
|
1976
|
+
const snapshots = collectRegistrySnapshotsForEnrichment(
|
|
1977
|
+
this.db,
|
|
1978
|
+
invocation.registryVersion
|
|
1979
|
+
);
|
|
1980
|
+
if (snapshots.length === 0) {
|
|
1981
|
+
this.markUnpriced(
|
|
1982
|
+
invocation,
|
|
1983
|
+
`Registry snapshot not found for version ${invocation.registryVersion}`
|
|
1984
|
+
);
|
|
1985
|
+
enrichedCount++;
|
|
1986
|
+
continue;
|
|
1987
|
+
}
|
|
1988
|
+
const reportedModelId = optionalAttributionId(invocation.reportedModelId);
|
|
1989
|
+
const evidence = this.db.getUsageEvidenceForInvocation(invocation.id);
|
|
1990
|
+
const modelEvidence = readModelEvidence(evidence?.availabilityJson);
|
|
1991
|
+
if (isAmbiguousModelEvidence(modelEvidence)) {
|
|
1992
|
+
this.markUnpriced(
|
|
1993
|
+
invocation,
|
|
1994
|
+
`Multiple models used${unverifiedModelSuffix(modelEvidence)}; cost unavailable`
|
|
1995
|
+
);
|
|
1996
|
+
enrichedCount++;
|
|
1997
|
+
continue;
|
|
1998
|
+
}
|
|
1999
|
+
const pricingModelIds = reportedModelId !== null && reportedModelId !== invocation.modelId ? modelEvidence?.source ? [reportedModelId] : [reportedModelId, invocation.modelId] : [invocation.modelId];
|
|
2000
|
+
let resolved = null;
|
|
2001
|
+
let resolvedPricingVersion = null;
|
|
2002
|
+
let providerSeen = false;
|
|
2003
|
+
let unpricedReason;
|
|
2004
|
+
for (const snapshot of snapshots) {
|
|
2005
|
+
const registry = parseJsonWithContext(
|
|
2006
|
+
snapshot.jsonBlob,
|
|
2007
|
+
"Model registry snapshot"
|
|
2008
|
+
);
|
|
2009
|
+
if (registry.providers.some((p) => p.providerId === invocation.providerId)) {
|
|
2010
|
+
providerSeen = true;
|
|
2011
|
+
}
|
|
2012
|
+
unpricedReason = findProviderAndModel(registry, invocation.providerId, invocation.modelId)?.model.unpricedReason;
|
|
2013
|
+
if (unpricedReason) break;
|
|
2014
|
+
for (const pricingModelId of pricingModelIds) {
|
|
2015
|
+
const candidate = findProviderAndModel(registry, invocation.providerId, pricingModelId);
|
|
2016
|
+
if (candidate) {
|
|
2017
|
+
resolved = candidate;
|
|
2018
|
+
resolvedPricingVersion = snapshot.registryVersion;
|
|
2019
|
+
break;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
if (resolved) break;
|
|
2023
|
+
}
|
|
2024
|
+
if (unpricedReason) {
|
|
2025
|
+
this.markUnpriced(invocation, unpricedReason);
|
|
2026
|
+
enrichedCount++;
|
|
2027
|
+
continue;
|
|
2028
|
+
}
|
|
2029
|
+
if (!resolved) {
|
|
2030
|
+
const reason = !providerSeen ? `Provider ${invocation.providerId} not found in registry` : `Model ${pricingModelIds.join(" / ")} not found for provider ${invocation.providerId}`;
|
|
2031
|
+
const unverified = unverifiedModelSuffix(modelEvidence);
|
|
2032
|
+
this.markUnpriced(invocation, `${reason}${unverified}`);
|
|
2033
|
+
enrichedCount++;
|
|
2034
|
+
continue;
|
|
2035
|
+
}
|
|
2036
|
+
const { model, resolvedModelId } = resolved;
|
|
2037
|
+
let pricingSource = model.costSchema?.pricing_source ?? "frontmatter";
|
|
2038
|
+
let pricingConfidence = model.costSchema?.pricing_confidence;
|
|
2039
|
+
let billingSource = model.costSchema?.billing_source;
|
|
2040
|
+
let rateCard = rateCardFromSnapshotCostSchema(model.costSchema ?? {});
|
|
2041
|
+
let extraFees = model.costSchema?.extra_fees ?? {};
|
|
2042
|
+
let billingMode = model.costSchema?.billing_mode ?? "metered";
|
|
2043
|
+
const overrideKey = `${invocation.providerId}:${invocation.modelId}`;
|
|
2044
|
+
const resolvedOverrideKey = `${invocation.providerId}:${resolvedModelId}`;
|
|
2045
|
+
const override = overrides?.[overrideKey] ?? (resolvedOverrideKey !== overrideKey ? overrides?.[resolvedOverrideKey] : void 0);
|
|
2046
|
+
if (override) {
|
|
2047
|
+
pricingSource = `override:${overrides?.[overrideKey] ? overrideKey : resolvedOverrideKey}`;
|
|
2048
|
+
rateCard = {
|
|
2049
|
+
inputPer1m: override.promptPer1k * 1e3,
|
|
2050
|
+
outputPer1m: override.completionPer1k * 1e3,
|
|
2051
|
+
...rateCard.longContext ? { longContext: rateCard.longContext } : {}
|
|
2052
|
+
};
|
|
2053
|
+
extraFees = override.extraFees ?? {};
|
|
2054
|
+
billingMode = "metered";
|
|
2055
|
+
pricingConfidence = void 0;
|
|
2056
|
+
billingSource = void 0;
|
|
2057
|
+
}
|
|
2058
|
+
if (billingMode === "metered" && !override && !declaresMeteredRates(model)) {
|
|
2059
|
+
this.markUnpriced(
|
|
2060
|
+
invocation,
|
|
2061
|
+
`No pricing schema for ${invocation.providerId}/${resolvedModelId}; cost unavailable${unverifiedModelSuffix(modelEvidence)}`
|
|
2062
|
+
);
|
|
2063
|
+
enrichedCount++;
|
|
2064
|
+
continue;
|
|
2065
|
+
}
|
|
2066
|
+
if (billingMode === "metered" && !hasPricedTokenEvidence(invocation, evidence)) {
|
|
2067
|
+
this.markUnpriced(
|
|
2068
|
+
invocation,
|
|
2069
|
+
`No usage evidence for invocation ${invocation.id}; cost unavailable`
|
|
2070
|
+
);
|
|
2071
|
+
enrichedCount++;
|
|
2072
|
+
continue;
|
|
2073
|
+
}
|
|
2074
|
+
const perModelKeys = evidence?.extraDimensionsJson ? Object.keys(JSON.parse(evidence.extraDimensionsJson)).filter((key) => key.startsWith("modelUsage/")).map((key) => key.split("/")[1]) : [];
|
|
2075
|
+
if (billingMode === "metered" && new Set(perModelKeys).size > 1) {
|
|
2076
|
+
this.markUnpriced(
|
|
2077
|
+
invocation,
|
|
2078
|
+
"Multiple reported models: aggregate usage cannot be priced as one model"
|
|
2079
|
+
);
|
|
2080
|
+
enrichedCount++;
|
|
2081
|
+
continue;
|
|
2082
|
+
}
|
|
2083
|
+
const tokensIn = evidence?.inputTokens ?? invocation.tokensIn ?? 0;
|
|
2084
|
+
const tokensOut = evidence?.outputTokens ?? invocation.tokensOut ?? 0;
|
|
2085
|
+
const cacheReadTokens = evidence?.cacheReadTokens ?? 0;
|
|
2086
|
+
const cacheWriteTokens = evidence?.cacheWriteTokens ?? 0;
|
|
2087
|
+
const cacheWriteLongTokens = readLongCacheWriteTokens(evidence?.extraDimensionsJson);
|
|
2088
|
+
const usageGranularity = usageGranularityFromEventSemantics(evidence?.eventSemantics);
|
|
2089
|
+
const requestedServiceTier = readRequestedServiceTier(evidence?.availabilityJson);
|
|
2090
|
+
const observedSpeed = readObservedSpeedBreakdown(evidence?.availabilityJson);
|
|
2091
|
+
const requestEvidence = readRequestEvidence(evidence?.availabilityJson);
|
|
2092
|
+
const listPrice = computeListPrice(
|
|
2093
|
+
rateCard,
|
|
2094
|
+
{
|
|
2095
|
+
inputTokens: tokensIn,
|
|
2096
|
+
outputTokens: tokensOut,
|
|
2097
|
+
cacheReadTokens,
|
|
2098
|
+
cacheWriteTokens,
|
|
2099
|
+
cacheWriteLongTokens,
|
|
2100
|
+
cacheReadIncludedInInput: evidence?.cacheReadIncludedInInput === true,
|
|
2101
|
+
cacheWriteIncludedInInput: evidence?.cacheWriteIncludedInInput === true,
|
|
2102
|
+
granularity: usageGranularity,
|
|
2103
|
+
requestEvidence
|
|
2104
|
+
},
|
|
2105
|
+
requestedServiceTier,
|
|
2106
|
+
{ speedBreakdown: observedSpeed, routed: model.costSchema?.routed_speed_tier }
|
|
2107
|
+
);
|
|
2108
|
+
const extraFeesCents = Object.values(extraFees).reduce(
|
|
2109
|
+
(sum, fee) => sum + (typeof fee === "number" ? fee * 100 : 0),
|
|
2110
|
+
0
|
|
2111
|
+
);
|
|
2112
|
+
let costCents = null;
|
|
2113
|
+
if (billingMode === "metered") {
|
|
2114
|
+
costCents = roundCostCents(listPrice.totalUsd * 100 + extraFeesCents);
|
|
2115
|
+
} else if (!model.costSchema?.pricing_source && !override) {
|
|
2116
|
+
pricingSource = `non_metered:${billingMode}`;
|
|
2117
|
+
}
|
|
2118
|
+
const originalModelId = typeof invocation.requestedModelId === "string" && invocation.requestedModelId.trim().length > 0 ? invocation.requestedModelId : invocation.modelId;
|
|
2119
|
+
const usageData = JSON.stringify({
|
|
2120
|
+
inputTokens: tokensIn,
|
|
2121
|
+
outputTokens: tokensOut,
|
|
2122
|
+
cacheReadTokens,
|
|
2123
|
+
cacheWriteTokens,
|
|
2124
|
+
// Persisted so recalculation prices the long-duration writes the same way.
|
|
2125
|
+
...cacheWriteLongTokens !== void 0 ? { cacheWriteLongTokens } : {},
|
|
2126
|
+
cacheReadTokensIncludedInInput: evidence?.cacheReadIncludedInInput === true,
|
|
2127
|
+
// Persisted so recalculation prices the record the same way.
|
|
2128
|
+
granularity: usageGranularity,
|
|
2129
|
+
// Persisted so recalculation reprices a fast run at the fast tier.
|
|
2130
|
+
...requestedServiceTier ? { requestedServiceTier } : {},
|
|
2131
|
+
...observedSpeed ? { observedSpeed } : {},
|
|
2132
|
+
...requestEvidence ? { requestEvidence } : {},
|
|
2133
|
+
originalModelId,
|
|
2134
|
+
canonicalModelId: resolvedModelId,
|
|
2135
|
+
// Present only when the provider reported one, so a reader can tell
|
|
2136
|
+
// "priced against what ran" from "priced against what was asked for".
|
|
2137
|
+
...reportedModelId !== null ? { reportedModelId } : {},
|
|
2138
|
+
...modelEvidence?.source ? { modelEvidenceSource: modelEvidence.source } : {}
|
|
2139
|
+
});
|
|
2140
|
+
const inputCostCents = billingMode === "metered" ? roundCostCents(listPrice.inputUsd * 100) : 0;
|
|
2141
|
+
const outputCostCents = billingMode === "metered" ? roundCostCents(listPrice.outputUsd * 100) : 0;
|
|
2142
|
+
const costSnapshot = JSON.stringify({
|
|
2143
|
+
inputCostCents,
|
|
2144
|
+
outputCostCents,
|
|
2145
|
+
cacheCostCents: billingMode === "metered" ? roundCostCents(listPrice.cacheUsd * 100) : 0,
|
|
2146
|
+
toolCostCents: 0,
|
|
2147
|
+
infrastructureCostCents: 0,
|
|
2148
|
+
totalCostCents: costCents,
|
|
2149
|
+
currency: "USD",
|
|
2150
|
+
pricingSource,
|
|
2151
|
+
...pricingConfidence ? { pricingConfidence } : {},
|
|
2152
|
+
billingMode,
|
|
2153
|
+
...billingSource ? { billingSource } : {},
|
|
2154
|
+
// 'not_assessed' flags a run aggregate priced at flat rates: the
|
|
2155
|
+
// long-context premium may apply to requests inside it, so this cost
|
|
2156
|
+
// can understate the list price.
|
|
2157
|
+
longContextPremium: listPrice.longContextPremium,
|
|
2158
|
+
...listPrice.longContextIncompleteReason ? { longContextIncompleteReason: listPrice.longContextIncompleteReason } : {},
|
|
2159
|
+
speedTier: listPrice.speedTier,
|
|
2160
|
+
originalModelId,
|
|
2161
|
+
canonicalModelId: resolvedModelId,
|
|
2162
|
+
...reportedModelId !== null ? { reportedModelId } : {},
|
|
2163
|
+
...modelEvidence?.source ? { modelEvidenceSource: modelEvidence.source } : {},
|
|
2164
|
+
pricingVersion: resolvedPricingVersion ?? invocation.registryVersion
|
|
2165
|
+
});
|
|
2166
|
+
const taskId = optionalAttributionId(invocation.taskId);
|
|
2167
|
+
const competitionId = optionalAttributionId(invocation.competitionId) ?? taskId;
|
|
2168
|
+
const variantId = optionalAttributionId(invocation.variantId);
|
|
2169
|
+
const enrichedId = generateInvocationId();
|
|
2170
|
+
const enriched = {
|
|
2171
|
+
id: enrichedId,
|
|
2172
|
+
invocationId: invocation.id,
|
|
2173
|
+
computedCostCents: costCents,
|
|
2174
|
+
pricingSource,
|
|
2175
|
+
enrichedAt: Date.now(),
|
|
2176
|
+
billingMode,
|
|
2177
|
+
usageData,
|
|
2178
|
+
costSnapshot,
|
|
2179
|
+
pricingVersion: resolvedPricingVersion ?? invocation.registryVersion,
|
|
2180
|
+
taskId,
|
|
2181
|
+
competitionId,
|
|
2182
|
+
variantId
|
|
2183
|
+
};
|
|
2184
|
+
this.db.insertEnrichedInvocation(enriched);
|
|
2185
|
+
const apiEquivalentCostUsd = costCents === null ? null : costCents / 100;
|
|
2186
|
+
const providerReportedCostUsd = invocation.providerReportedCostUsd ?? null;
|
|
2187
|
+
const evaluationIdentity = JSON.stringify({
|
|
2188
|
+
invocationId: invocation.id,
|
|
2189
|
+
evidenceId: evidence?.evidenceId ?? null,
|
|
2190
|
+
pricingVersion: resolvedPricingVersion ?? invocation.registryVersion,
|
|
2191
|
+
methodologyVersion: "api-equivalent-v1",
|
|
2192
|
+
apiEquivalentCostUsd,
|
|
2193
|
+
providerReportedCostUsd
|
|
2194
|
+
});
|
|
2195
|
+
this.db.insertCostEvaluation({
|
|
2196
|
+
evaluationId: createHash2("sha256").update(evaluationIdentity).digest("hex"),
|
|
2197
|
+
invocationId: invocation.id,
|
|
2198
|
+
evidenceId: evidence?.evidenceId ?? null,
|
|
2199
|
+
pricingVersion: resolvedPricingVersion ?? invocation.registryVersion,
|
|
2200
|
+
methodologyVersion: "api-equivalent-v1",
|
|
2201
|
+
apiEquivalentCostUsd,
|
|
2202
|
+
providerReportedCostUsd,
|
|
2203
|
+
comparisonDeltaUsd: apiEquivalentCostUsd !== null && providerReportedCostUsd !== null ? providerReportedCostUsd - apiEquivalentCostUsd : null,
|
|
2204
|
+
currency: model.costSchema?.currency ?? "USD",
|
|
2205
|
+
// A premium or tier the list price could not assess is partial, not fully priced.
|
|
2206
|
+
status: apiEquivalentCostUsd === null ? "unavailable" : listPrice.complete ? "complete" : "partial",
|
|
2207
|
+
createdAt: Date.now()
|
|
2208
|
+
});
|
|
2209
|
+
enrichedCount++;
|
|
2210
|
+
} catch (error) {
|
|
2211
|
+
this.error(
|
|
2212
|
+
`Failed to enrich invocation ${invocation.id}:`,
|
|
2213
|
+
error instanceof Error ? error.message : String(error)
|
|
2214
|
+
);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
return enrichedCount;
|
|
2218
|
+
}
|
|
2219
|
+
/**
|
|
2220
|
+
* Aggregate enriched invocations into daily cost buckets.
|
|
2221
|
+
* Returns number of daily aggregate rows upserted.
|
|
2222
|
+
*/
|
|
2223
|
+
aggregateDaily() {
|
|
2224
|
+
return this.db.backfillDailyCostAggregations();
|
|
2225
|
+
}
|
|
2226
|
+
/**
|
|
2227
|
+
* Run full enrichment and aggregation cycle
|
|
2228
|
+
*/
|
|
2229
|
+
async runCycle(overrides) {
|
|
2230
|
+
const enrichedCount = await this.enrichInvocations(overrides);
|
|
2231
|
+
const dailyAggregatedCount = this.aggregateDaily();
|
|
2232
|
+
return { enrichedCount, dailyAggregatedCount };
|
|
2233
|
+
}
|
|
2234
|
+
};
|
|
2235
|
+
|
|
2236
|
+
// src/telemetry/invocation-cost-enrichment.ts
|
|
2237
|
+
var PENDING_ENRICHMENT_HINT = "Invocations exist in this window but cost enrichment is pending or incomplete.";
|
|
2238
|
+
var NO_INVOCATIONS_HINT = "No model invocations recorded in this window.";
|
|
2239
|
+
var ZERO_USAGE_HINT = "Invocations were recorded but reported zero token usage; per-token cost is $0.";
|
|
2240
|
+
var MISSING_USAGE_HINT = "Invocations were recorded without usage evidence; their cost is unavailable, not $0.";
|
|
2241
|
+
var MISSING_PRICING_HINT = "Invocations reported usage but no pricing was available for their model; their cost is unavailable, not $0.";
|
|
2242
|
+
function pricingMetadata(pricing) {
|
|
2243
|
+
if (!pricing) return {};
|
|
2244
|
+
const pricingSource = pricingSourceFromRow(pricing);
|
|
2245
|
+
const pricingConfidence = pricingConfidenceFromRow(pricing);
|
|
2246
|
+
const billingSource = billingSourceFromRow(pricing);
|
|
2247
|
+
return {
|
|
2248
|
+
...pricingSource ? { pricing_source: pricingSource } : {},
|
|
2249
|
+
...pricingConfidence ? { pricing_confidence: pricingConfidence } : {},
|
|
2250
|
+
...billingSource ? { billing_source: billingSource } : {}
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
function passthroughPricingRow(parsed, providerId, modelId) {
|
|
2254
|
+
const target = passthroughResolver.getPassthroughTarget(
|
|
2255
|
+
providerId,
|
|
2256
|
+
modelId,
|
|
2257
|
+
parsed.providers[providerId]?.pricing,
|
|
2258
|
+
(provider, model) => parsed.providers[provider]?.pricing?.models?.[model] !== void 0
|
|
2259
|
+
);
|
|
2260
|
+
const upstreamRow = target && parsed.providers[target.provider]?.pricing?.models?.[target.model];
|
|
2261
|
+
if (!target || !upstreamRow) return void 0;
|
|
2262
|
+
const row = target.speed_tier ? pricingRowAtSpeedTier(upstreamRow, target.speed_tier) : upstreamRow;
|
|
2263
|
+
if (!snapshotCostSchemaFromRow(row)) return void 0;
|
|
2264
|
+
const tier = target.speed_tier ? upstreamRow.speed_tiers?.[target.speed_tier] : void 0;
|
|
2265
|
+
const unpublishedAbove = tier?.long_context_pricing ? void 0 : upstreamRow.long_context_pricing?.threshold_tokens;
|
|
2266
|
+
const routedSpeedTier2 = target.speed_tier ? {
|
|
2267
|
+
requested: target.speed_tier,
|
|
2268
|
+
applied: tier ? target.speed_tier : "standard",
|
|
2269
|
+
...tier && unpublishedAbove !== void 0 ? { unpublishedLongContextAboveTokens: unpublishedAbove } : {}
|
|
2270
|
+
} : void 0;
|
|
2271
|
+
return {
|
|
2272
|
+
row,
|
|
2273
|
+
upstream: `${target.provider}/${target.model}`,
|
|
2274
|
+
...routedSpeedTier2 ? { routedSpeedTier: routedSpeedTier2 } : {},
|
|
2275
|
+
...target.note ? { note: target.note } : {}
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
function buildInvocationRegistry(parsed) {
|
|
2279
|
+
const providers = Object.entries(parsed.providers).map(
|
|
2280
|
+
([providerId, provider]) => ({
|
|
2281
|
+
providerId,
|
|
2282
|
+
displayName: provider.display_name,
|
|
2283
|
+
modelMappings: { ...provider.models.mappings },
|
|
2284
|
+
models: provider.models.available.map((model) => {
|
|
2285
|
+
const declared = provider.pricing?.models?.[model.id];
|
|
2286
|
+
const unpricedReason = declared?.unpriced_reason;
|
|
2287
|
+
const passthrough = declared?.non_metered || declared && snapshotCostSchemaFromRow(declared) ? void 0 : passthroughPricingRow(parsed, providerId, model.id);
|
|
2288
|
+
const pricing = passthrough?.row ?? declared;
|
|
2289
|
+
const per1kRates = pricing ? snapshotCostSchemaFromRow(pricing) : void 0;
|
|
2290
|
+
const metadata = passthrough ? {
|
|
2291
|
+
...pricingSourceFromRow(passthrough.row) ? { pricing_source: pricingSourceFromRow(passthrough.row) } : {},
|
|
2292
|
+
resolution_route: `passthrough:${passthrough.upstream}`
|
|
2293
|
+
} : pricingMetadata(pricing);
|
|
2294
|
+
return {
|
|
2295
|
+
modelId: model.id,
|
|
2296
|
+
displayName: model.display_name,
|
|
2297
|
+
capabilities: model.capabilities,
|
|
2298
|
+
...pricing?.non_metered ? {
|
|
2299
|
+
costSchema: {
|
|
2300
|
+
billing_mode: "non_metered",
|
|
2301
|
+
...metadata,
|
|
2302
|
+
notes: pricing.notes
|
|
2303
|
+
}
|
|
2304
|
+
} : per1kRates ? {
|
|
2305
|
+
costSchema: {
|
|
2306
|
+
...per1kRates,
|
|
2307
|
+
currency: provider.pricing?.currency ?? "USD",
|
|
2308
|
+
billing_mode: "metered",
|
|
2309
|
+
...metadata,
|
|
2310
|
+
...passthrough?.routedSpeedTier ? { routed_speed_tier: passthrough.routedSpeedTier } : {},
|
|
2311
|
+
notes: joinPricingNotes(
|
|
2312
|
+
pricing?.notes,
|
|
2313
|
+
passthrough?.note,
|
|
2314
|
+
passthrough?.routedSpeedTier?.applied === "standard" ? `${passthrough.routedSpeedTier.requested} requested, priced at standard.` : void 0
|
|
2315
|
+
)
|
|
2316
|
+
}
|
|
2317
|
+
} : {},
|
|
2318
|
+
...unpricedReason ? { unpricedReason } : {}
|
|
2319
|
+
};
|
|
2320
|
+
})
|
|
2321
|
+
})
|
|
2322
|
+
);
|
|
2323
|
+
return buildRegistry(providers);
|
|
2324
|
+
}
|
|
2325
|
+
function prepareInvocationRegistry() {
|
|
2326
|
+
try {
|
|
2327
|
+
return buildInvocationRegistry(ProviderRegistry.getActiveRegistry());
|
|
2328
|
+
} catch (error) {
|
|
2329
|
+
debugLog(
|
|
2330
|
+
`[Telemetry] Active registry snapshot unavailable: ${error instanceof Error ? error.message : String(error)}`
|
|
2331
|
+
);
|
|
2332
|
+
return null;
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
function captureInvocationRegistryVersion(db, prepared) {
|
|
2336
|
+
try {
|
|
2337
|
+
const registry = prepared === void 0 ? prepareInvocationRegistry() : prepared;
|
|
2338
|
+
if (!registry) return null;
|
|
2339
|
+
db.insertRegistrySnapshot({
|
|
2340
|
+
registryVersion: registry.registryVersion,
|
|
2341
|
+
createdAt: Date.now(),
|
|
2342
|
+
jsonBlob: JSON.stringify(registry)
|
|
2343
|
+
});
|
|
2344
|
+
return registry.registryVersion;
|
|
2345
|
+
} catch {
|
|
2346
|
+
return null;
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
function ensureRegistrySnapshot(db) {
|
|
2350
|
+
return captureInvocationRegistryVersion(db) !== null;
|
|
2351
|
+
}
|
|
2352
|
+
function ensureRegistrySnapshotForPendingEnrichment(db, options) {
|
|
2353
|
+
const existingSnapshot = db.getLatestRegistrySnapshot();
|
|
2354
|
+
if (options.refreshRegistrySnapshot === false && existingSnapshot) {
|
|
2355
|
+
return true;
|
|
2356
|
+
}
|
|
2357
|
+
return ensureRegistrySnapshot(db);
|
|
2358
|
+
}
|
|
2359
|
+
async function ensurePendingInvocationCostEnrichment(db, options = {}) {
|
|
2360
|
+
const pendingCount = db.countUnenrichedInvocations();
|
|
2361
|
+
if (pendingCount === 0) {
|
|
2362
|
+
return { enrichedCount: 0, registrySnapshotEnsured: Boolean(db.getLatestRegistrySnapshot()) };
|
|
2363
|
+
}
|
|
2364
|
+
const registrySnapshotEnsured = ensureRegistrySnapshotForPendingEnrichment(db, options);
|
|
2365
|
+
if (!registrySnapshotEnsured) {
|
|
2366
|
+
return { enrichedCount: 0, registrySnapshotEnsured: false };
|
|
2367
|
+
}
|
|
2368
|
+
const worker = new TelemetryWorker(db, { diagnostics: "silent" });
|
|
2369
|
+
const enrichedCount = options.aggregate === false ? await worker.enrichInvocations() : (await worker.runCycle()).enrichedCount;
|
|
2370
|
+
return {
|
|
2371
|
+
enrichedCount,
|
|
2372
|
+
registrySnapshotEnsured: true
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
function resolveUnpricedBreakdown(db, startTimestampMs, unpricedInvocationCount) {
|
|
2376
|
+
if (unpricedInvocationCount === 0) {
|
|
2377
|
+
return {};
|
|
2378
|
+
}
|
|
2379
|
+
const unpricedMissingPricingCount = Math.min(
|
|
2380
|
+
unpricedInvocationCount,
|
|
2381
|
+
db.countUnpricedWithTokenEvidenceSince(startTimestampMs)
|
|
2382
|
+
);
|
|
2383
|
+
return {
|
|
2384
|
+
unpricedCause: unpricedMissingPricingCount > 0 ? "missing_pricing" : "missing_usage",
|
|
2385
|
+
unpricedMissingPricingCount
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
var UNPRICED_CAUSE_HINTS = {
|
|
2389
|
+
missing_pricing: MISSING_PRICING_HINT,
|
|
2390
|
+
missing_usage: MISSING_USAGE_HINT
|
|
2391
|
+
};
|
|
2392
|
+
function listInvocationProviderIdsSince(db, startTimestampMs) {
|
|
2393
|
+
try {
|
|
2394
|
+
const rows = db.db.prepare("SELECT DISTINCT provider_id FROM model_invocations WHERE timestamp >= ?").all(startTimestampMs);
|
|
2395
|
+
return rows.map((row) => row.provider_id.toLowerCase());
|
|
2396
|
+
} catch {
|
|
2397
|
+
return void 0;
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
function summarizeInvocationCostVisibility(db, startTimestampMs) {
|
|
2401
|
+
const rawInvocationCount = db.countRawInvocationsSince(startTimestampMs);
|
|
2402
|
+
const enrichedInvocationCount = db.countEnrichedInvocationsSince(startTimestampMs);
|
|
2403
|
+
const pendingEnrichmentCount = Math.max(0, rawInvocationCount - enrichedInvocationCount);
|
|
2404
|
+
const unpricedInvocationCount = db.countUnpricedInvocationsSince(startTimestampMs);
|
|
2405
|
+
const unpricedBreakdown = resolveUnpricedBreakdown(db, startTimestampMs, unpricedInvocationCount);
|
|
2406
|
+
const unpricedCause = unpricedBreakdown.unpricedCause;
|
|
2407
|
+
const counts = {
|
|
2408
|
+
rawInvocationCount,
|
|
2409
|
+
enrichedInvocationCount,
|
|
2410
|
+
pendingEnrichmentCount,
|
|
2411
|
+
unpricedInvocationCount,
|
|
2412
|
+
...unpricedBreakdown
|
|
2413
|
+
};
|
|
2414
|
+
if (rawInvocationCount === 0) {
|
|
2415
|
+
return {
|
|
2416
|
+
...counts,
|
|
2417
|
+
visibility: "no_invocations",
|
|
2418
|
+
hint: NO_INVOCATIONS_HINT
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
if (pendingEnrichmentCount > 0) {
|
|
2422
|
+
return {
|
|
2423
|
+
...counts,
|
|
2424
|
+
visibility: "pending_enrichment",
|
|
2425
|
+
hint: PENDING_ENRICHMENT_HINT
|
|
2426
|
+
};
|
|
2427
|
+
}
|
|
2428
|
+
const hasTokenUsage = db.hasTokenUsageSince(startTimestampMs);
|
|
2429
|
+
if (!hasTokenUsage) {
|
|
2430
|
+
return {
|
|
2431
|
+
...counts,
|
|
2432
|
+
visibility: "zero_usage",
|
|
2433
|
+
// A window with no positive usage is a real $0 only when every row was
|
|
2434
|
+
// priced; unpriced rows get the explanation their cause calls for.
|
|
2435
|
+
hint: unpricedCause ? UNPRICED_CAUSE_HINTS[unpricedCause] : ZERO_USAGE_HINT
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
return {
|
|
2439
|
+
...counts,
|
|
2440
|
+
visibility: "enriched",
|
|
2441
|
+
// Fully enriched is not fully priced. A window whose usage was positive
|
|
2442
|
+
// still needs the pricing gap named, or the only thing an operator sees is
|
|
2443
|
+
// a bare count of rows the total left out.
|
|
2444
|
+
...unpricedCause ? { hint: UNPRICED_CAUSE_HINTS[unpricedCause] } : {}
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
// src/telemetry/cost-projection.ts
|
|
2449
|
+
function availableCostAmount(amount, unit, source, detail) {
|
|
2450
|
+
return {
|
|
2451
|
+
amount,
|
|
2452
|
+
unit,
|
|
2453
|
+
status: "available",
|
|
2454
|
+
source,
|
|
2455
|
+
...detail ? { detail } : {}
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
function unavailableCostAmount(unit, source, detail) {
|
|
2459
|
+
return {
|
|
2460
|
+
amount: null,
|
|
2461
|
+
unit,
|
|
2462
|
+
status: "unavailable",
|
|
2463
|
+
source,
|
|
2464
|
+
...detail ? { detail } : {}
|
|
2465
|
+
};
|
|
2466
|
+
}
|
|
2467
|
+
function errorCostAmount(unit, source, error, detail) {
|
|
2468
|
+
return {
|
|
2469
|
+
amount: null,
|
|
2470
|
+
unit,
|
|
2471
|
+
status: "error",
|
|
2472
|
+
source,
|
|
2473
|
+
error,
|
|
2474
|
+
...detail ? { detail } : {}
|
|
2475
|
+
};
|
|
2476
|
+
}
|
|
2477
|
+
function isAvailableCostAmount(value) {
|
|
2478
|
+
return value.status === "available";
|
|
2479
|
+
}
|
|
2480
|
+
function costAmountOrNull(value) {
|
|
2481
|
+
return value.status === "available" ? value.amount : null;
|
|
2482
|
+
}
|
|
2483
|
+
var PENDING_HINT = "Invocations exist in this window but cost enrichment is pending; totals may under-report until enrichment completes.";
|
|
2484
|
+
var FAILED_HINT = "Invocations exist in this window but cost enrichment could not run (no pricing snapshot available); showing raw counts only.";
|
|
2485
|
+
var RAW_ZERO_USAGE_HINT = "Invocations were recorded but reported zero token usage; per-token cost is $0.";
|
|
2486
|
+
var NO_INVOCATIONS_HINT2 = "No model invocations recorded in this window.";
|
|
2487
|
+
function mapVisibilityToState(summary, attempt) {
|
|
2488
|
+
switch (summary.visibility) {
|
|
2489
|
+
case "enriched":
|
|
2490
|
+
return "enriched";
|
|
2491
|
+
case "zero_usage":
|
|
2492
|
+
return "raw";
|
|
2493
|
+
case "no_invocations":
|
|
2494
|
+
return "enriched";
|
|
2495
|
+
case "pending_enrichment":
|
|
2496
|
+
if (attempt && attempt.registrySnapshotEnsured === false) {
|
|
2497
|
+
return "failed";
|
|
2498
|
+
}
|
|
2499
|
+
return "pending";
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
function resolveHint(state, summary) {
|
|
2503
|
+
switch (state) {
|
|
2504
|
+
case "pending":
|
|
2505
|
+
return PENDING_HINT;
|
|
2506
|
+
case "failed":
|
|
2507
|
+
return FAILED_HINT;
|
|
2508
|
+
case "raw":
|
|
2509
|
+
return summary.hint ?? RAW_ZERO_USAGE_HINT;
|
|
2510
|
+
case "enriched":
|
|
2511
|
+
if (summary.rawInvocationCount === 0) {
|
|
2512
|
+
return NO_INVOCATIONS_HINT2;
|
|
2513
|
+
}
|
|
2514
|
+
return summary.hint;
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
function projectCostVisibility(db, startTimestampMs, attempt) {
|
|
2518
|
+
const summary = summarizeInvocationCostVisibility(db, startTimestampMs);
|
|
2519
|
+
return fromVisibilitySummary(summary, attempt);
|
|
2520
|
+
}
|
|
2521
|
+
function fromVisibilitySummary(summary, attempt) {
|
|
2522
|
+
const state = mapVisibilityToState(summary, attempt);
|
|
2523
|
+
const totalsMayUnderReport = state === "pending" || state === "failed" || summary.unpricedInvocationCount > 0;
|
|
2524
|
+
const hint = resolveHint(state, summary);
|
|
2525
|
+
return {
|
|
2526
|
+
rawInvocationCount: summary.rawInvocationCount,
|
|
2527
|
+
enrichedInvocationCount: summary.enrichedInvocationCount,
|
|
2528
|
+
pendingEnrichmentCount: summary.pendingEnrichmentCount,
|
|
2529
|
+
unpricedInvocationCount: summary.unpricedInvocationCount,
|
|
2530
|
+
...summary.unpricedCause ? { unpricedCause: summary.unpricedCause } : {},
|
|
2531
|
+
...summary.unpricedMissingPricingCount !== void 0 ? { unpricedMissingPricingCount: summary.unpricedMissingPricingCount } : {},
|
|
2532
|
+
state,
|
|
2533
|
+
visibility: summary.visibility,
|
|
2534
|
+
totalsMayUnderReport,
|
|
2535
|
+
...hint ? { hint } : {}
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2538
|
+
function formatCostProjectionNotice(projection) {
|
|
2539
|
+
const unpriced = formatUnpricedClause(
|
|
2540
|
+
projection.unpricedInvocationCount,
|
|
2541
|
+
projection.unpricedMissingPricingCount
|
|
2542
|
+
);
|
|
2543
|
+
if (projection.state === "enriched" && projection.rawInvocationCount === 0) {
|
|
2544
|
+
return NO_INVOCATIONS_HINT2;
|
|
2545
|
+
}
|
|
2546
|
+
if (projection.state === "enriched") {
|
|
2547
|
+
return unpriced;
|
|
2548
|
+
}
|
|
2549
|
+
const counts = projection.pendingEnrichmentCount > 0 ? ` (${projection.pendingEnrichmentCount} pending of ${projection.rawInvocationCount} raw)` : projection.rawInvocationCount > 0 ? ` (${projection.rawInvocationCount} raw)` : "";
|
|
2550
|
+
return [`${projection.hint ?? ""}${counts}`.trim(), unpriced].filter(Boolean).join("; ") || void 0;
|
|
2551
|
+
}
|
|
2552
|
+
function formatUnpricedClause(unpricedInvocationCount, missingPricingCount) {
|
|
2553
|
+
if (unpricedInvocationCount <= 0) {
|
|
2554
|
+
return void 0;
|
|
2555
|
+
}
|
|
2556
|
+
const excluded = "(cost unavailable, excluded from totals)";
|
|
2557
|
+
const missingUsageCount = missingPricingCount === void 0 ? void 0 : unpricedInvocationCount - missingPricingCount;
|
|
2558
|
+
if (missingPricingCount !== void 0 && missingPricingCount > 0 && missingUsageCount) {
|
|
2559
|
+
return `${unpricedInvocationCount} invocations unpriced: ${missingPricingCount} reported usage but no pricing was available; ${missingUsageCount} recorded without usage evidence ${excluded}`;
|
|
2560
|
+
}
|
|
2561
|
+
const noun = unpricedInvocationCount === 1 ? "invocation" : "invocations";
|
|
2562
|
+
const missing = missingPricingCount === void 0 ? "without usage evidence or pricing" : missingPricingCount > 0 ? "reported usage but no pricing was available" : "without usage evidence";
|
|
2563
|
+
return `${unpricedInvocationCount} ${noun} ${missing} ${excluded}`;
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
// src/telemetry/imported-cost-records.ts
|
|
2567
|
+
function loadImportedCostRecords(imported, local, projectSyncId, startMs, endExclusiveMs) {
|
|
2568
|
+
const scope = `r.scope_key = 'production'${projectSyncId ? " AND b.project_sync_id = ?" : ""}`;
|
|
2569
|
+
const projectArgs = projectSyncId ? [projectSyncId] : [];
|
|
2570
|
+
const bounds = [];
|
|
2571
|
+
const boundArgs = [];
|
|
2572
|
+
if (startMs !== void 0) {
|
|
2573
|
+
bounds.push("CAST(json_extract(r.payload_json, '$.timestamp') AS REAL) >= ?");
|
|
2574
|
+
boundArgs.push(startMs);
|
|
2575
|
+
}
|
|
2576
|
+
if (endExclusiveMs !== void 0) {
|
|
2577
|
+
bounds.push("CAST(json_extract(r.payload_json, '$.timestamp') AS REAL) < ?");
|
|
2578
|
+
boundArgs.push(endExclusiveMs);
|
|
2579
|
+
}
|
|
2580
|
+
const invocations = imported.prepare(`
|
|
2581
|
+
WITH latest AS (
|
|
2582
|
+
SELECT MAX(r.rowid) as rowId, MIN(r.rowid) as firstRowId
|
|
2583
|
+
FROM portability_records r
|
|
2584
|
+
JOIN portability_bundles b ON b.bundle_id = r.bundle_id
|
|
2585
|
+
WHERE ${scope} AND r.record_type = 'invocation'
|
|
2586
|
+
GROUP BY json_extract(r.payload_json, '$.invocationId')
|
|
2587
|
+
)
|
|
2588
|
+
SELECT r.record_type as recordType, r.payload_json as payloadJson,
|
|
2589
|
+
json_extract(r.payload_json, '$.invocationId') as invocationId
|
|
2590
|
+
FROM latest JOIN portability_records r ON r.rowid = latest.rowId
|
|
2591
|
+
WHERE 1 = 1 ${bounds.map((bound) => `AND ${bound}`).join(" ")}
|
|
2592
|
+
ORDER BY latest.firstRowId
|
|
2593
|
+
`).all(...projectArgs, ...boundArgs);
|
|
2594
|
+
const selectedIds = [
|
|
2595
|
+
...new Set(
|
|
2596
|
+
invocations.map((row) => row.invocationId).filter((id) => typeof id === "string")
|
|
2597
|
+
)
|
|
2598
|
+
];
|
|
2599
|
+
if (selectedIds.length === 0) return [];
|
|
2600
|
+
const localIds = new Set(
|
|
2601
|
+
local.prepare(`
|
|
2602
|
+
SELECT id FROM model_invocations WHERE id IN (SELECT value FROM json_each(?))
|
|
2603
|
+
`).all(JSON.stringify(selectedIds)).map((row) => row.id)
|
|
2604
|
+
);
|
|
2605
|
+
const remoteIds = selectedIds.filter((id) => !localIds.has(id));
|
|
2606
|
+
if (remoteIds.length === 0) return [];
|
|
2607
|
+
const evidence = imported.prepare(`
|
|
2608
|
+
SELECT r.record_type as recordType, r.payload_json as payloadJson
|
|
2609
|
+
FROM portability_records r
|
|
2610
|
+
JOIN portability_bundles b ON b.bundle_id = r.bundle_id
|
|
2611
|
+
WHERE ${scope} AND r.record_type IN ('usage', 'costEvaluation')
|
|
2612
|
+
AND json_extract(r.payload_json, '$.invocationId')
|
|
2613
|
+
IN (SELECT value FROM json_each(?))
|
|
2614
|
+
`).all(...projectArgs, JSON.stringify(remoteIds));
|
|
2615
|
+
return [
|
|
2616
|
+
...invocations.filter(
|
|
2617
|
+
(row) => typeof row.invocationId === "string" && !localIds.has(row.invocationId)
|
|
2618
|
+
),
|
|
2619
|
+
...evidence
|
|
2620
|
+
];
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
// src/telemetry/invocation-quality.ts
|
|
2624
|
+
var SAFE_SQL_ALIAS = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
2625
|
+
function invocationProvenanceClauses(alias) {
|
|
2626
|
+
const prefix = sqlPrefix(alias);
|
|
2627
|
+
return [
|
|
2628
|
+
`${prefix}provider_id <> 'unknown'`,
|
|
2629
|
+
`(${prefix}execution_source IS NULL OR ${prefix}execution_source = 'live')`
|
|
2630
|
+
];
|
|
2631
|
+
}
|
|
2632
|
+
function invocationMetricQualityClauses(alias) {
|
|
2633
|
+
const prefix = sqlPrefix(alias);
|
|
2634
|
+
return [
|
|
2635
|
+
`NOT (${prefix}total_time_ms IS NOT NULL AND ${prefix}total_time_ms < 1000 AND ${prefix}tokens_out IS NULL)`,
|
|
2636
|
+
`NOT (
|
|
2637
|
+
${prefix}tokens_out IS NOT NULL
|
|
2638
|
+
AND ${prefix}tokens_out > 0
|
|
2639
|
+
AND ${prefix}time_first_token_ms IS NOT NULL
|
|
2640
|
+
AND ${prefix}time_last_token_ms IS NOT NULL
|
|
2641
|
+
AND ${prefix}time_last_token_ms > ${prefix}time_first_token_ms
|
|
2642
|
+
AND ((${prefix}tokens_out * 1000.0) / (${prefix}time_last_token_ms - ${prefix}time_first_token_ms)) > 1000
|
|
2643
|
+
)`
|
|
2644
|
+
];
|
|
2645
|
+
}
|
|
2646
|
+
function invocationAnalyticsQualityClauses(alias) {
|
|
2647
|
+
return [...invocationProvenanceClauses(alias), ...invocationMetricQualityClauses(alias)];
|
|
2648
|
+
}
|
|
2649
|
+
function invocationProvenanceWhereSql(alias) {
|
|
2650
|
+
return clausesToAndSql(invocationProvenanceClauses(alias));
|
|
2651
|
+
}
|
|
2652
|
+
function isInvocationMetricQualityRow(row) {
|
|
2653
|
+
if (row.total_time_ms !== null && row.total_time_ms !== void 0 && row.total_time_ms < 1e3) {
|
|
2654
|
+
if (row.tokens_out === null || row.tokens_out === void 0) return false;
|
|
2655
|
+
}
|
|
2656
|
+
const tokensOut = row.tokens_out;
|
|
2657
|
+
const firstTokenMs = row.time_first_token_ms;
|
|
2658
|
+
const lastTokenMs = row.time_last_token_ms;
|
|
2659
|
+
if (typeof tokensOut === "number" && tokensOut > 0 && typeof firstTokenMs === "number" && typeof lastTokenMs === "number" && lastTokenMs > firstTokenMs) {
|
|
2660
|
+
const decodeTokensPerSecond = tokensOut * 1e3 / (lastTokenMs - firstTokenMs);
|
|
2661
|
+
if (decodeTokensPerSecond > 1e3) return false;
|
|
2662
|
+
}
|
|
2663
|
+
return true;
|
|
2664
|
+
}
|
|
2665
|
+
function clausesToAndSql(clauses) {
|
|
2666
|
+
if (clauses.length === 0) return "";
|
|
2667
|
+
return clauses.map((clause) => `AND ${clause}`).join("\n");
|
|
2668
|
+
}
|
|
2669
|
+
function sqlPrefix(alias) {
|
|
2670
|
+
if (alias === void 0) return "";
|
|
2671
|
+
if (!SAFE_SQL_ALIAS.test(alias)) {
|
|
2672
|
+
throw new Error(`Invalid SQL alias: ${alias}`);
|
|
2673
|
+
}
|
|
2674
|
+
return `${alias}.`;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
// src/telemetry/model-invocations-db.ts
|
|
2678
|
+
var ENRICHED_INVOCATIONS_SOURCE = "enriched_invocations";
|
|
2679
|
+
var EXECUTION_COST_COLUMNS = `
|
|
2680
|
+
e.invocation_id,
|
|
2681
|
+
i.timestamp,
|
|
2682
|
+
i.provider_id,
|
|
2683
|
+
i.model_id,
|
|
2684
|
+
e.usage_data,
|
|
2685
|
+
e.cost_snapshot,
|
|
2686
|
+
e.computed_cost_cents,
|
|
2687
|
+
e.billing_mode,
|
|
2688
|
+
e.pricing_version,
|
|
2689
|
+
e.pricing_source,
|
|
2690
|
+
e.task_id,
|
|
2691
|
+
e.competition_id,
|
|
2692
|
+
i.agent_role,
|
|
2693
|
+
e.variant_id
|
|
2694
|
+
`;
|
|
2695
|
+
function parseExecutionCostUsage(usageData) {
|
|
2696
|
+
return parseJsonWithContext(usageData, "Execution usage data");
|
|
2697
|
+
}
|
|
2698
|
+
function resolveExecutionCostSnapshot(row) {
|
|
2699
|
+
const costSnapshot = row.cost_snapshot ? parseJsonWithContext(row.cost_snapshot, "Cost snapshot") : null;
|
|
2700
|
+
return resolveSnapshotFirstCostSnapshot({
|
|
2701
|
+
costSnapshot,
|
|
2702
|
+
computedCostCents: row.computed_cost_cents,
|
|
2703
|
+
billingMode: row.billing_mode
|
|
2704
|
+
});
|
|
2705
|
+
}
|
|
2706
|
+
function projectExecutionCostRecord(row, usage, costSnapshot) {
|
|
2707
|
+
return {
|
|
2708
|
+
executionId: row.invocation_id,
|
|
2709
|
+
timestamp: new Date(row.timestamp),
|
|
2710
|
+
provider: row.provider_id,
|
|
2711
|
+
model: row.model_id,
|
|
2712
|
+
usage,
|
|
2713
|
+
pricingVersion: row.pricing_version || "unknown",
|
|
2714
|
+
pricingSource: row.pricing_source || "xml",
|
|
2715
|
+
costSnapshot,
|
|
2716
|
+
computedCostCents: row.computed_cost_cents ?? void 0,
|
|
2717
|
+
taskId: row.task_id ?? void 0,
|
|
2718
|
+
competitionId: row.competition_id ?? void 0,
|
|
2719
|
+
agentRole: row.agent_role ?? void 0,
|
|
2720
|
+
variantId: row.variant_id ?? void 0
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2723
|
+
function createInvocationAnalyticsViews(db) {
|
|
2724
|
+
const views = [
|
|
2725
|
+
{
|
|
2726
|
+
name: "invocation_metrics_v",
|
|
2727
|
+
ddl: `
|
|
2728
|
+
CREATE VIEW invocation_metrics_v AS
|
|
2729
|
+
WITH ranked_usage AS (
|
|
2730
|
+
SELECT
|
|
2731
|
+
u.*,
|
|
2732
|
+
ROW_NUMBER() OVER (
|
|
2733
|
+
PARTITION BY u.invocation_id
|
|
2734
|
+
ORDER BY
|
|
2735
|
+
CASE u.event_semantics
|
|
2736
|
+
WHEN 'final' THEN 4
|
|
2737
|
+
WHEN 'cumulative' THEN 3
|
|
2738
|
+
WHEN 'logical_run_aggregate' THEN 2
|
|
2739
|
+
ELSE 1
|
|
2740
|
+
END DESC,
|
|
2741
|
+
u.created_at DESC,
|
|
2742
|
+
u.evidence_id DESC
|
|
2743
|
+
) AS preference_rank,
|
|
2744
|
+
SUM(
|
|
2745
|
+
CASE WHEN u.event_semantics IN ('final', 'cumulative', 'logical_run_aggregate')
|
|
2746
|
+
THEN 1 ELSE 0 END
|
|
2747
|
+
) OVER (PARTITION BY u.invocation_id) AS terminal_count
|
|
2748
|
+
FROM usage_evidence u
|
|
2749
|
+
),
|
|
2750
|
+
usage_reduced AS (
|
|
2751
|
+
SELECT
|
|
2752
|
+
invocation_id,
|
|
2753
|
+
input_tokens,
|
|
2754
|
+
output_tokens,
|
|
2755
|
+
cache_read_tokens,
|
|
2756
|
+
cache_write_tokens,
|
|
2757
|
+
reasoning_tokens,
|
|
2758
|
+
tool_tokens,
|
|
2759
|
+
cache_read_included_in_input,
|
|
2760
|
+
cache_write_included_in_input,
|
|
2761
|
+
provenance AS usage_provenance,
|
|
2762
|
+
event_semantics,
|
|
2763
|
+
availability_json
|
|
2764
|
+
FROM ranked_usage
|
|
2765
|
+
WHERE terminal_count > 0 AND preference_rank = 1
|
|
2766
|
+
|
|
2767
|
+
UNION ALL
|
|
2768
|
+
|
|
2769
|
+
SELECT
|
|
2770
|
+
invocation_id,
|
|
2771
|
+
SUM(input_tokens),
|
|
2772
|
+
SUM(output_tokens),
|
|
2773
|
+
SUM(cache_read_tokens),
|
|
2774
|
+
SUM(cache_write_tokens),
|
|
2775
|
+
SUM(reasoning_tokens),
|
|
2776
|
+
SUM(tool_tokens),
|
|
2777
|
+
MAX(cache_read_included_in_input),
|
|
2778
|
+
MAX(cache_write_included_in_input),
|
|
2779
|
+
'derived',
|
|
2780
|
+
'delta',
|
|
2781
|
+
'{}'
|
|
2782
|
+
FROM ranked_usage
|
|
2783
|
+
WHERE terminal_count = 0
|
|
2784
|
+
GROUP BY invocation_id
|
|
2785
|
+
),
|
|
2786
|
+
ranked_cost AS (
|
|
2787
|
+
SELECT
|
|
2788
|
+
c.*,
|
|
2789
|
+
ROW_NUMBER() OVER (
|
|
2790
|
+
PARTITION BY c.invocation_id
|
|
2791
|
+
ORDER BY c.created_at DESC, c.evaluation_id DESC
|
|
2792
|
+
) AS cost_rank
|
|
2793
|
+
FROM cost_evaluations c
|
|
2794
|
+
)
|
|
2795
|
+
SELECT
|
|
2796
|
+
i.id AS invocation_id,
|
|
2797
|
+
i.project_id,
|
|
2798
|
+
i.lab_id,
|
|
2799
|
+
i.lab_run_id,
|
|
2800
|
+
i.run_id,
|
|
2801
|
+
i.task_id,
|
|
2802
|
+
i.competition_id,
|
|
2803
|
+
i.variant_id,
|
|
2804
|
+
i.attempt_id,
|
|
2805
|
+
i.attempt_ordinal,
|
|
2806
|
+
i.agent_role,
|
|
2807
|
+
i.provider_id,
|
|
2808
|
+
i.model_id,
|
|
2809
|
+
i.status,
|
|
2810
|
+
i.error_code,
|
|
2811
|
+
i.timestamp,
|
|
2812
|
+
i.time_request_sent_ms,
|
|
2813
|
+
i.time_first_token_ms,
|
|
2814
|
+
i.time_last_token_ms,
|
|
2815
|
+
i.time_to_first_token_ms,
|
|
2816
|
+
i.total_time_ms,
|
|
2817
|
+
COALESCE(u.input_tokens, i.tokens_in) AS input_tokens,
|
|
2818
|
+
COALESCE(u.output_tokens, i.tokens_out) AS output_tokens,
|
|
2819
|
+
u.cache_read_tokens,
|
|
2820
|
+
u.cache_write_tokens,
|
|
2821
|
+
u.reasoning_tokens,
|
|
2822
|
+
u.tool_tokens,
|
|
2823
|
+
u.cache_read_included_in_input,
|
|
2824
|
+
u.cache_write_included_in_input,
|
|
2825
|
+
u.usage_provenance,
|
|
2826
|
+
u.event_semantics,
|
|
2827
|
+
u.availability_json,
|
|
2828
|
+
COALESCE(c.api_equivalent_cost_usd, i.build_calculated_cost_usd) AS api_equivalent_cost_usd,
|
|
2829
|
+
COALESCE(c.provider_reported_cost_usd, i.provider_reported_cost_usd) AS provider_reported_cost_usd,
|
|
2830
|
+
c.comparison_delta_usd,
|
|
2831
|
+
COALESCE(
|
|
2832
|
+
c.status,
|
|
2833
|
+
CASE WHEN i.build_calculated_cost_usd IS NOT NULL THEN 'complete' ELSE 'unavailable' END
|
|
2834
|
+
) AS cost_status,
|
|
2835
|
+
c.pricing_version,
|
|
2836
|
+
c.methodology_version
|
|
2837
|
+
FROM model_invocations i
|
|
2838
|
+
LEFT JOIN usage_reduced u ON u.invocation_id = i.id
|
|
2839
|
+
LEFT JOIN ranked_cost c ON c.invocation_id = i.id AND c.cost_rank = 1
|
|
2840
|
+
`
|
|
2841
|
+
},
|
|
2842
|
+
{
|
|
2843
|
+
name: "task_invocation_rollup_v",
|
|
2844
|
+
ddl: `
|
|
2845
|
+
CREATE VIEW task_invocation_rollup_v AS
|
|
2846
|
+
SELECT
|
|
2847
|
+
project_id,
|
|
2848
|
+
lab_id,
|
|
2849
|
+
lab_run_id,
|
|
2850
|
+
run_id,
|
|
2851
|
+
task_id,
|
|
2852
|
+
MAX(timestamp) AS last_invocation_ms,
|
|
2853
|
+
COUNT(*) AS invocation_count,
|
|
2854
|
+
SUM(CASE WHEN input_tokens IS NOT NULL AND output_tokens IS NOT NULL THEN 1 ELSE 0 END)
|
|
2855
|
+
AS usage_reported_count,
|
|
2856
|
+
SUM(
|
|
2857
|
+
CASE WHEN cost_status = 'complete' AND api_equivalent_cost_usd IS NOT NULL
|
|
2858
|
+
THEN 1 ELSE 0 END
|
|
2859
|
+
) AS priced_count,
|
|
2860
|
+
MIN(time_request_sent_ms) AS provider_started_ms,
|
|
2861
|
+
MAX(time_last_token_ms) AS provider_finished_ms,
|
|
2862
|
+
SUM(total_time_ms) AS provider_sum_ms,
|
|
2863
|
+
SUM(input_tokens) AS input_tokens,
|
|
2864
|
+
SUM(output_tokens) AS output_tokens,
|
|
2865
|
+
SUM(cache_read_tokens) AS cache_read_tokens,
|
|
2866
|
+
SUM(cache_write_tokens) AS cache_write_tokens,
|
|
2867
|
+
SUM(reasoning_tokens) AS reasoning_tokens,
|
|
2868
|
+
SUM(tool_tokens) AS tool_tokens,
|
|
2869
|
+
SUM(api_equivalent_cost_usd) AS api_equivalent_cost_usd,
|
|
2870
|
+
SUM(provider_reported_cost_usd) AS provider_reported_cost_usd
|
|
2871
|
+
FROM invocation_metrics_v
|
|
2872
|
+
GROUP BY project_id, lab_id, lab_run_id, run_id, task_id
|
|
2873
|
+
`
|
|
2874
|
+
}
|
|
2875
|
+
];
|
|
2876
|
+
const normalizeDdl = (value) => value.replace(/\s+/g, " ").trim().toLowerCase();
|
|
2877
|
+
const staleViews = views.filter((view) => {
|
|
2878
|
+
const existing = db.prepare("SELECT sql FROM sqlite_master WHERE type = 'view' AND name = ?").get(view.name);
|
|
2879
|
+
return !existing?.sql || normalizeDdl(existing.sql) !== normalizeDdl(view.ddl);
|
|
2880
|
+
});
|
|
2881
|
+
if (staleViews.length > 0) {
|
|
2882
|
+
try {
|
|
2883
|
+
db.exec("BEGIN IMMEDIATE");
|
|
2884
|
+
for (const view of staleViews) {
|
|
2885
|
+
db.exec(`DROP VIEW IF EXISTS ${view.name}`);
|
|
2886
|
+
db.exec(view.ddl);
|
|
2887
|
+
}
|
|
2888
|
+
db.exec("COMMIT");
|
|
2889
|
+
} catch (error) {
|
|
2890
|
+
try {
|
|
2891
|
+
db.exec("ROLLBACK");
|
|
2892
|
+
} catch {
|
|
2893
|
+
}
|
|
2894
|
+
emitWarning(
|
|
2895
|
+
"WS-003",
|
|
2896
|
+
{
|
|
2897
|
+
taskId: "system",
|
|
2898
|
+
subsystem: "telemetry",
|
|
2899
|
+
executionPhase: "setup"
|
|
2900
|
+
},
|
|
2901
|
+
{
|
|
2902
|
+
severity: "WARN" /* WARN */,
|
|
2903
|
+
metadata: {
|
|
2904
|
+
operation: "create_invocation_analytics_views",
|
|
2905
|
+
views: staleViews.map((view) => view.name).join(","),
|
|
2906
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
);
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
for (const view of views) {
|
|
2913
|
+
try {
|
|
2914
|
+
db.prepare(`SELECT * FROM ${view.name} LIMIT 0`).all();
|
|
2915
|
+
} catch (error) {
|
|
2916
|
+
emitWarning(
|
|
2917
|
+
"WS-003",
|
|
2918
|
+
{
|
|
2919
|
+
taskId: "system",
|
|
2920
|
+
subsystem: "telemetry",
|
|
2921
|
+
executionPhase: "setup"
|
|
2922
|
+
},
|
|
2923
|
+
{
|
|
2924
|
+
severity: "WARN" /* WARN */,
|
|
2925
|
+
metadata: {
|
|
2926
|
+
operation: "verify_invocation_analytics_view",
|
|
2927
|
+
view: view.name,
|
|
2928
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
);
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
var ALLOWED_TABLE_NAMES = [
|
|
2936
|
+
"model_invocations",
|
|
2937
|
+
"registry_snapshots",
|
|
2938
|
+
"enriched_invocations",
|
|
2939
|
+
"usage_evidence",
|
|
2940
|
+
"cost_evaluations",
|
|
2941
|
+
"aggregates_hourly",
|
|
2942
|
+
"cost_aggregations_daily"
|
|
2943
|
+
];
|
|
2944
|
+
var SAFE_SQL_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
2945
|
+
var SAFE_COLUMN_DEFINITION = /^(TEXT|INTEGER|REAL|BLOB|NUMERIC)$/iu;
|
|
2946
|
+
function validateTableName(tableName) {
|
|
2947
|
+
if (!ALLOWED_TABLE_NAMES.includes(tableName)) {
|
|
2948
|
+
throw new Error(
|
|
2949
|
+
`Invalid table name: "${tableName}". Only the following tables are allowed: ${ALLOWED_TABLE_NAMES.join(", ")}`
|
|
2950
|
+
);
|
|
2951
|
+
}
|
|
2952
|
+
return tableName;
|
|
2953
|
+
}
|
|
2954
|
+
function validateSqlIdentifier(value, label) {
|
|
2955
|
+
if (!SAFE_SQL_IDENTIFIER.test(value)) {
|
|
2956
|
+
throw new Error(`Invalid SQL ${label}: ${value}`);
|
|
2957
|
+
}
|
|
2958
|
+
return value;
|
|
2959
|
+
}
|
|
2960
|
+
function validateColumnDefinition(value) {
|
|
2961
|
+
if (!SAFE_COLUMN_DEFINITION.test(value)) {
|
|
2962
|
+
throw new Error(`Invalid SQL column definition: ${value}`);
|
|
2963
|
+
}
|
|
2964
|
+
return value;
|
|
2965
|
+
}
|
|
2966
|
+
function snakeToCamel(str) {
|
|
2967
|
+
return str.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
2968
|
+
}
|
|
2969
|
+
function convertRowToCamelCase(row) {
|
|
2970
|
+
if (!row) return null;
|
|
2971
|
+
const result = {};
|
|
2972
|
+
for (const key in row) {
|
|
2973
|
+
result[snakeToCamel(key)] = row[key];
|
|
2974
|
+
}
|
|
2975
|
+
return result;
|
|
2976
|
+
}
|
|
2977
|
+
function sumNullableEvidence(rows, key) {
|
|
2978
|
+
const values = rows.map((row) => row[key]).filter((value) => typeof value === "number");
|
|
2979
|
+
return values.length === 0 ? null : values.reduce((sum, value) => sum + value, 0);
|
|
2980
|
+
}
|
|
2981
|
+
function reducePortableUsage(rows) {
|
|
2982
|
+
if (!rows || rows.length === 0) return void 0;
|
|
2983
|
+
const final = rows.filter(
|
|
2984
|
+
(row) => ["final", "cumulative", "logical_run_aggregate"].includes(String(row.eventSemantics))
|
|
2985
|
+
).sort((left, right) => Number(left.createdAt ?? 0) - Number(right.createdAt ?? 0)).at(-1);
|
|
2986
|
+
if (final) return final;
|
|
2987
|
+
const summed = { ...rows.at(-1), eventSemantics: "delta" };
|
|
2988
|
+
for (const key of [
|
|
2989
|
+
"inputTokens",
|
|
2990
|
+
"outputTokens",
|
|
2991
|
+
"cacheReadTokens",
|
|
2992
|
+
"cacheWriteTokens",
|
|
2993
|
+
"reasoningTokens",
|
|
2994
|
+
"toolTokens"
|
|
2995
|
+
]) {
|
|
2996
|
+
const values = rows.map((row) => row[key]).filter((value) => typeof value === "number");
|
|
2997
|
+
summed[key] = values.length === 0 ? null : values.reduce((sum, value) => sum + value, 0);
|
|
2998
|
+
}
|
|
2999
|
+
return summed;
|
|
3000
|
+
}
|
|
3001
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
3002
|
+
function utcDateStartMs(date) {
|
|
3003
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/u.exec(date);
|
|
3004
|
+
if (!match) {
|
|
3005
|
+
throw new Error(`Invalid date (expected YYYY-MM-DD): ${date}`);
|
|
3006
|
+
}
|
|
3007
|
+
const year = Number(match[1]);
|
|
3008
|
+
const month = Number(match[2]);
|
|
3009
|
+
const day = Number(match[3]);
|
|
3010
|
+
const ms = Date.UTC(year, month - 1, day);
|
|
3011
|
+
const check = new Date(ms);
|
|
3012
|
+
if (check.getUTCFullYear() !== year || check.getUTCMonth() !== month - 1 || check.getUTCDate() !== day) {
|
|
3013
|
+
throw new Error(`Invalid calendar date: ${date}`);
|
|
3014
|
+
}
|
|
3015
|
+
return ms;
|
|
3016
|
+
}
|
|
3017
|
+
var compatibleModelIndexByProvider = /* @__PURE__ */ new Map();
|
|
3018
|
+
registerProviderRegistryReloadInvalidator(() => {
|
|
3019
|
+
compatibleModelIndexByProvider.clear();
|
|
3020
|
+
});
|
|
3021
|
+
function addAlternate(index, alternate, modelId) {
|
|
3022
|
+
if (typeof alternate !== "string") return;
|
|
3023
|
+
const trimmed = alternate.trim();
|
|
3024
|
+
if (trimmed.length === 0) return;
|
|
3025
|
+
let owners = index.alternateToModels.get(trimmed);
|
|
3026
|
+
if (!owners) {
|
|
3027
|
+
owners = /* @__PURE__ */ new Set();
|
|
3028
|
+
index.alternateToModels.set(trimmed, owners);
|
|
3029
|
+
}
|
|
3030
|
+
owners.add(modelId);
|
|
3031
|
+
}
|
|
3032
|
+
function buildCompatibleModelIndex(providerId) {
|
|
3033
|
+
try {
|
|
3034
|
+
const config = ProviderRegistry.getProviderConfig(providerId);
|
|
3035
|
+
const index = {
|
|
3036
|
+
modelIds: /* @__PURE__ */ new Set(),
|
|
3037
|
+
alternateToModels: /* @__PURE__ */ new Map()
|
|
3038
|
+
};
|
|
3039
|
+
for (const model of config.models.available) {
|
|
3040
|
+
index.modelIds.add(model.id);
|
|
3041
|
+
addAlternate(index, model.id, model.id);
|
|
3042
|
+
addAlternate(index, model.full_name, model.id);
|
|
3043
|
+
addAlternate(index, model.canonical_id, model.id);
|
|
3044
|
+
addAlternate(index, model.routing_id, model.id);
|
|
3045
|
+
for (const routeId of listModelRouteIds(model)) addAlternate(index, routeId, model.id);
|
|
3046
|
+
}
|
|
3047
|
+
return index;
|
|
3048
|
+
} catch {
|
|
3049
|
+
return null;
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
function getCompatibleModelIndex(providerId) {
|
|
3053
|
+
const trimmedProvider = providerId.trim();
|
|
3054
|
+
if (trimmedProvider.length === 0) return null;
|
|
3055
|
+
if (compatibleModelIndexByProvider.has(trimmedProvider)) {
|
|
3056
|
+
return compatibleModelIndexByProvider.get(trimmedProvider) ?? null;
|
|
3057
|
+
}
|
|
3058
|
+
const index = buildCompatibleModelIndex(trimmedProvider);
|
|
3059
|
+
compatibleModelIndexByProvider.set(trimmedProvider, index);
|
|
3060
|
+
return index;
|
|
3061
|
+
}
|
|
3062
|
+
function lookupCompatibleModelId(index, candidate) {
|
|
3063
|
+
if (index.modelIds.has(candidate)) {
|
|
3064
|
+
return candidate;
|
|
3065
|
+
}
|
|
3066
|
+
const owners = index.alternateToModels.get(candidate);
|
|
3067
|
+
if (owners && owners.size === 1) {
|
|
3068
|
+
return owners.values().next().value;
|
|
3069
|
+
}
|
|
3070
|
+
return null;
|
|
3071
|
+
}
|
|
3072
|
+
function stripOneTrailingContextSelector(modelId) {
|
|
3073
|
+
const match = modelId.match(/^(.*)(\[[^\]]+\])\s*$/u);
|
|
3074
|
+
if (!match || match[1] === void 0 || match[2] === void 0) {
|
|
3075
|
+
return null;
|
|
3076
|
+
}
|
|
3077
|
+
const base = match[1].trim();
|
|
3078
|
+
if (base.length === 0) {
|
|
3079
|
+
return null;
|
|
3080
|
+
}
|
|
3081
|
+
return base;
|
|
3082
|
+
}
|
|
3083
|
+
function resolveCompatibleInvocationModelId(providerId, modelId) {
|
|
3084
|
+
const trimmedModel = typeof modelId === "string" ? modelId.trim() : "";
|
|
3085
|
+
if (trimmedModel.length === 0) return modelId;
|
|
3086
|
+
const index = getCompatibleModelIndex(providerId);
|
|
3087
|
+
if (!index) return modelId;
|
|
3088
|
+
const direct = lookupCompatibleModelId(index, trimmedModel);
|
|
3089
|
+
if (direct !== null) {
|
|
3090
|
+
return direct;
|
|
3091
|
+
}
|
|
3092
|
+
const baseWithoutSelector = stripOneTrailingContextSelector(trimmedModel);
|
|
3093
|
+
if (baseWithoutSelector !== null && baseWithoutSelector !== trimmedModel) {
|
|
3094
|
+
const fromBase = lookupCompatibleModelId(index, baseWithoutSelector);
|
|
3095
|
+
if (fromBase !== null) {
|
|
3096
|
+
return fromBase;
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
return modelId;
|
|
3100
|
+
}
|
|
3101
|
+
function invocationModelMatchesSameModel(providerId, storedModelId, filterModelId) {
|
|
3102
|
+
if (storedModelId === filterModelId) return true;
|
|
3103
|
+
const storedNorm = resolveCompatibleInvocationModelId(providerId, storedModelId);
|
|
3104
|
+
const filterNorm = resolveCompatibleInvocationModelId(providerId, filterModelId);
|
|
3105
|
+
return storedNorm === filterNorm;
|
|
3106
|
+
}
|
|
3107
|
+
var ModelInvocationsDB = class {
|
|
3108
|
+
// Exposed for worker access - ideally should add public query methods instead
|
|
3109
|
+
db;
|
|
3110
|
+
// Database instance type (better-sqlite3 or in-memory shim)
|
|
3111
|
+
dbPath;
|
|
3112
|
+
portabilityDbDir;
|
|
3113
|
+
projectSyncId;
|
|
3114
|
+
constructor(dbPath, options = {}) {
|
|
3115
|
+
if (!sqlite_wrapper_default) {
|
|
3116
|
+
throw new Error("Database implementation not available");
|
|
3117
|
+
}
|
|
3118
|
+
this.dbPath = dbPath;
|
|
3119
|
+
this.portabilityDbDir = options.portabilityDbDir;
|
|
3120
|
+
this.projectSyncId = options.projectSyncId;
|
|
3121
|
+
mkdirSync2(path5.dirname(dbPath), { recursive: true });
|
|
3122
|
+
this.db = new sqlite_wrapper_default(dbPath);
|
|
3123
|
+
this.initializeSchema();
|
|
3124
|
+
}
|
|
3125
|
+
/**
|
|
3126
|
+
* Initialize database schema with all required tables
|
|
3127
|
+
*/
|
|
3128
|
+
initializeSchema() {
|
|
3129
|
+
applySqlitePragmas(this.db, { foreignKeys: true });
|
|
3130
|
+
this.db.exec(`
|
|
3131
|
+
-- Model invocations table
|
|
3132
|
+
CREATE TABLE IF NOT EXISTS model_invocations (
|
|
3133
|
+
id TEXT PRIMARY KEY,
|
|
3134
|
+
timestamp INTEGER NOT NULL,
|
|
3135
|
+
parent_workflow_id TEXT,
|
|
3136
|
+
provider_id TEXT NOT NULL,
|
|
3137
|
+
model_id TEXT NOT NULL,
|
|
3138
|
+
registry_version TEXT NOT NULL,
|
|
3139
|
+
tokens_in INTEGER,
|
|
3140
|
+
tokens_out INTEGER,
|
|
3141
|
+
time_request_sent_ms INTEGER NOT NULL,
|
|
3142
|
+
time_first_token_ms INTEGER,
|
|
3143
|
+
time_last_token_ms INTEGER,
|
|
3144
|
+
time_to_first_token_ms INTEGER,
|
|
3145
|
+
total_time_ms INTEGER,
|
|
3146
|
+
status TEXT NOT NULL,
|
|
3147
|
+
error_code TEXT,
|
|
3148
|
+
request_size_bytes INTEGER,
|
|
3149
|
+
agent_role TEXT,
|
|
3150
|
+
copilot_subcommand TEXT,
|
|
3151
|
+
execution_source TEXT,
|
|
3152
|
+
requested_model_id TEXT,
|
|
3153
|
+
execution_model_id TEXT,
|
|
3154
|
+
reported_model_id TEXT,
|
|
3155
|
+
backend TEXT,
|
|
3156
|
+
scope TEXT,
|
|
3157
|
+
context_selector TEXT,
|
|
3158
|
+
resolution_source TEXT,
|
|
3159
|
+
task_id TEXT,
|
|
3160
|
+
competition_id TEXT,
|
|
3161
|
+
variant_id TEXT
|
|
3162
|
+
,run_id TEXT
|
|
3163
|
+
,attempt_id TEXT
|
|
3164
|
+
,attempt_ordinal INTEGER
|
|
3165
|
+
,project_id TEXT
|
|
3166
|
+
,lab_id TEXT
|
|
3167
|
+
,lab_run_id TEXT
|
|
3168
|
+
,provider_reported_cost_usd REAL
|
|
3169
|
+
,build_calculated_cost_usd REAL
|
|
3170
|
+
);
|
|
3171
|
+
|
|
3172
|
+
CREATE INDEX IF NOT EXISTS idx_model_invocations_timestamp
|
|
3173
|
+
ON model_invocations(timestamp);
|
|
3174
|
+
CREATE INDEX IF NOT EXISTS idx_model_invocations_provider_model
|
|
3175
|
+
ON model_invocations(provider_id, model_id);
|
|
3176
|
+
CREATE INDEX IF NOT EXISTS idx_model_invocations_registry_version
|
|
3177
|
+
ON model_invocations(registry_version);
|
|
3178
|
+
CREATE INDEX IF NOT EXISTS idx_model_invocations_agent_role
|
|
3179
|
+
ON model_invocations(agent_role);
|
|
3180
|
+
-- Partial index for speed/latency analytics, which filter on
|
|
3181
|
+
-- status='success' (+ optional timestamp/provider/model) and order by
|
|
3182
|
+
-- timestamp. A PARTIAL index keyed only to successful rows is used
|
|
3183
|
+
-- instead of the full composite (status, timestamp, provider_id,
|
|
3184
|
+
-- model_id) deliberately: the full composite would add per-INSERT write
|
|
3185
|
+
-- cost on the hot invocation-write path for every row, whereas success
|
|
3186
|
+
-- rows are the only ones these read paths target. Validate that the
|
|
3187
|
+
-- speed query actually uses this index with EXPLAIN QUERY PLAN. Created
|
|
3188
|
+
-- idempotently like the other indexes above (no SCHEMA_VERSION bump).
|
|
3189
|
+
CREATE INDEX IF NOT EXISTS idx_model_invocations_success_speed
|
|
3190
|
+
ON model_invocations(timestamp DESC, provider_id, model_id)
|
|
3191
|
+
WHERE status = 'success';
|
|
3192
|
+
|
|
3193
|
+
-- Registry snapshots table
|
|
3194
|
+
CREATE TABLE IF NOT EXISTS registry_snapshots (
|
|
3195
|
+
registry_version TEXT PRIMARY KEY,
|
|
3196
|
+
created_at INTEGER NOT NULL,
|
|
3197
|
+
json_blob TEXT NOT NULL
|
|
3198
|
+
);
|
|
3199
|
+
|
|
3200
|
+
-- Enriched invocations table (hybrid storage: tokens + cost snapshot)
|
|
3201
|
+
CREATE TABLE IF NOT EXISTS enriched_invocations (
|
|
3202
|
+
id TEXT PRIMARY KEY,
|
|
3203
|
+
invocation_id TEXT NOT NULL UNIQUE,
|
|
3204
|
+
computed_cost_cents INTEGER,
|
|
3205
|
+
pricing_source TEXT NOT NULL,
|
|
3206
|
+
enriched_at INTEGER NOT NULL,
|
|
3207
|
+
billing_mode TEXT,
|
|
3208
|
+
|
|
3209
|
+
-- Hybrid Storage: Raw usage data (JSON) - immutable source of truth
|
|
3210
|
+
usage_data TEXT,
|
|
3211
|
+
|
|
3212
|
+
-- Hybrid Storage: Cost snapshot (JSON) - historical accuracy
|
|
3213
|
+
cost_snapshot TEXT,
|
|
3214
|
+
|
|
3215
|
+
-- Hybrid Storage: Pricing metadata for recalculation
|
|
3216
|
+
pricing_version TEXT,
|
|
3217
|
+
|
|
3218
|
+
-- Hybrid Storage: Context for cost attribution
|
|
3219
|
+
task_id TEXT,
|
|
3220
|
+
competition_id TEXT,
|
|
3221
|
+
variant_id TEXT,
|
|
3222
|
+
|
|
3223
|
+
FOREIGN KEY (invocation_id) REFERENCES model_invocations(id)
|
|
3224
|
+
);
|
|
3225
|
+
|
|
3226
|
+
CREATE INDEX IF NOT EXISTS idx_enriched_invocations_invocation_id
|
|
3227
|
+
ON enriched_invocations(invocation_id);
|
|
3228
|
+
CREATE INDEX IF NOT EXISTS idx_enriched_invocations_task_id
|
|
3229
|
+
ON enriched_invocations(task_id);
|
|
3230
|
+
CREATE INDEX IF NOT EXISTS idx_enriched_invocations_competition_id
|
|
3231
|
+
ON enriched_invocations(competition_id);
|
|
3232
|
+
|
|
3233
|
+
CREATE TABLE IF NOT EXISTS usage_evidence (
|
|
3234
|
+
evidence_id TEXT PRIMARY KEY,
|
|
3235
|
+
invocation_id TEXT NOT NULL,
|
|
3236
|
+
run_id TEXT NOT NULL,
|
|
3237
|
+
attempt_id TEXT NOT NULL,
|
|
3238
|
+
event_semantics TEXT NOT NULL,
|
|
3239
|
+
provenance TEXT NOT NULL,
|
|
3240
|
+
input_tokens INTEGER,
|
|
3241
|
+
output_tokens INTEGER,
|
|
3242
|
+
cache_read_tokens INTEGER,
|
|
3243
|
+
cache_write_tokens INTEGER,
|
|
3244
|
+
reasoning_tokens INTEGER,
|
|
3245
|
+
tool_tokens INTEGER,
|
|
3246
|
+
cache_read_included_in_input INTEGER,
|
|
3247
|
+
cache_write_included_in_input INTEGER,
|
|
3248
|
+
extra_dimensions_json TEXT,
|
|
3249
|
+
availability_json TEXT NOT NULL,
|
|
3250
|
+
parser_version TEXT NOT NULL,
|
|
3251
|
+
created_at INTEGER NOT NULL,
|
|
3252
|
+
FOREIGN KEY (invocation_id) REFERENCES model_invocations(id)
|
|
3253
|
+
);
|
|
3254
|
+
CREATE INDEX IF NOT EXISTS idx_usage_evidence_invocation
|
|
3255
|
+
ON usage_evidence(invocation_id);
|
|
3256
|
+
CREATE INDEX IF NOT EXISTS idx_usage_evidence_run
|
|
3257
|
+
ON usage_evidence(run_id, attempt_id);
|
|
3258
|
+
|
|
3259
|
+
CREATE TABLE IF NOT EXISTS cost_evaluations (
|
|
3260
|
+
evaluation_id TEXT PRIMARY KEY,
|
|
3261
|
+
invocation_id TEXT NOT NULL,
|
|
3262
|
+
evidence_id TEXT,
|
|
3263
|
+
pricing_version TEXT,
|
|
3264
|
+
methodology_version TEXT NOT NULL,
|
|
3265
|
+
api_equivalent_cost_usd REAL,
|
|
3266
|
+
provider_reported_cost_usd REAL,
|
|
3267
|
+
comparison_delta_usd REAL,
|
|
3268
|
+
currency TEXT NOT NULL,
|
|
3269
|
+
status TEXT NOT NULL,
|
|
3270
|
+
created_at INTEGER NOT NULL,
|
|
3271
|
+
FOREIGN KEY (invocation_id) REFERENCES model_invocations(id),
|
|
3272
|
+
FOREIGN KEY (evidence_id) REFERENCES usage_evidence(evidence_id)
|
|
3273
|
+
);
|
|
3274
|
+
CREATE INDEX IF NOT EXISTS idx_cost_evaluations_invocation
|
|
3275
|
+
ON cost_evaluations(invocation_id, created_at);
|
|
3276
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_cost_evaluations_methodology
|
|
3277
|
+
ON cost_evaluations(invocation_id, methodology_version, pricing_version);
|
|
3278
|
+
|
|
3279
|
+
-- Hourly aggregates table: retained schema only; nothing writes or reads it
|
|
3280
|
+
CREATE TABLE IF NOT EXISTS aggregates_hourly (
|
|
3281
|
+
bucket_start INTEGER NOT NULL,
|
|
3282
|
+
provider_id TEXT NOT NULL,
|
|
3283
|
+
model_id TEXT NOT NULL,
|
|
3284
|
+
total_invocations INTEGER NOT NULL,
|
|
3285
|
+
total_tokens_in INTEGER NOT NULL,
|
|
3286
|
+
total_tokens_out INTEGER NOT NULL,
|
|
3287
|
+
avg_cost_cents REAL NOT NULL,
|
|
3288
|
+
p95_latency_ms INTEGER NOT NULL,
|
|
3289
|
+
error_rate REAL NOT NULL,
|
|
3290
|
+
PRIMARY KEY (bucket_start, provider_id, model_id)
|
|
3291
|
+
);
|
|
3292
|
+
|
|
3293
|
+
-- Daily cost aggregates table (queried by getDailyCostAggregations)
|
|
3294
|
+
CREATE TABLE IF NOT EXISTS cost_aggregations_daily (
|
|
3295
|
+
date TEXT NOT NULL,
|
|
3296
|
+
provider TEXT NOT NULL,
|
|
3297
|
+
model TEXT NOT NULL,
|
|
3298
|
+
execution_count INTEGER NOT NULL DEFAULT 0,
|
|
3299
|
+
total_cost_usd REAL NOT NULL DEFAULT 0,
|
|
3300
|
+
total_input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
3301
|
+
total_output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
3302
|
+
avg_cost_per_execution REAL NOT NULL DEFAULT 0,
|
|
3303
|
+
PRIMARY KEY (date, provider, model)
|
|
3304
|
+
);
|
|
3305
|
+
CREATE INDEX IF NOT EXISTS idx_cost_aggregations_daily_date
|
|
3306
|
+
ON cost_aggregations_daily(date);
|
|
3307
|
+
`);
|
|
3308
|
+
this.ensureColumn("model_invocations", "copilot_subcommand", "TEXT");
|
|
3309
|
+
this.ensureColumn("model_invocations", "execution_source", "TEXT");
|
|
3310
|
+
this.ensureColumn("model_invocations", "requested_model_id", "TEXT");
|
|
3311
|
+
this.ensureColumn("model_invocations", "execution_model_id", "TEXT");
|
|
3312
|
+
this.ensureColumn("model_invocations", "reported_model_id", "TEXT");
|
|
3313
|
+
this.ensureColumn("model_invocations", "backend", "TEXT");
|
|
3314
|
+
this.ensureColumn("model_invocations", "scope", "TEXT");
|
|
3315
|
+
this.ensureColumn("model_invocations", "context_selector", "TEXT");
|
|
3316
|
+
this.ensureColumn("model_invocations", "resolution_source", "TEXT");
|
|
3317
|
+
this.ensureColumn("model_invocations", "task_id", "TEXT");
|
|
3318
|
+
this.ensureColumn("model_invocations", "competition_id", "TEXT");
|
|
3319
|
+
this.ensureColumn("model_invocations", "variant_id", "TEXT");
|
|
3320
|
+
this.ensureColumn("model_invocations", "run_id", "TEXT");
|
|
3321
|
+
this.ensureColumn("model_invocations", "attempt_id", "TEXT");
|
|
3322
|
+
this.ensureColumn("model_invocations", "attempt_ordinal", "INTEGER");
|
|
3323
|
+
this.ensureColumn("model_invocations", "project_id", "TEXT");
|
|
3324
|
+
this.ensureColumn("model_invocations", "lab_id", "TEXT");
|
|
3325
|
+
this.ensureColumn("model_invocations", "lab_run_id", "TEXT");
|
|
3326
|
+
this.ensureColumn("model_invocations", "provider_reported_cost_usd", "REAL");
|
|
3327
|
+
this.ensureColumn("model_invocations", "build_calculated_cost_usd", "REAL");
|
|
3328
|
+
this.ensureColumn("enriched_invocations", "billing_mode", "TEXT");
|
|
3329
|
+
this.ensureColumn("enriched_invocations", "usage_data", "TEXT");
|
|
3330
|
+
this.ensureColumn("enriched_invocations", "cost_snapshot", "TEXT");
|
|
3331
|
+
this.ensureColumn("enriched_invocations", "pricing_version", "TEXT");
|
|
3332
|
+
this.ensureColumn("enriched_invocations", "task_id", "TEXT");
|
|
3333
|
+
this.ensureColumn("enriched_invocations", "competition_id", "TEXT");
|
|
3334
|
+
this.ensureColumn("enriched_invocations", "variant_id", "TEXT");
|
|
3335
|
+
this.db.exec(`
|
|
3336
|
+
CREATE INDEX IF NOT EXISTS idx_model_invocations_task_time
|
|
3337
|
+
ON model_invocations(task_id, timestamp);
|
|
3338
|
+
CREATE INDEX IF NOT EXISTS idx_model_invocations_run
|
|
3339
|
+
ON model_invocations(run_id);
|
|
3340
|
+
CREATE INDEX IF NOT EXISTS idx_model_invocations_project_time
|
|
3341
|
+
ON model_invocations(project_id, timestamp);
|
|
3342
|
+
`);
|
|
3343
|
+
createInvocationAnalyticsViews(this.db);
|
|
3344
|
+
}
|
|
3345
|
+
/**
|
|
3346
|
+
* Ensures a column exists in a table, adding it if necessary.
|
|
3347
|
+
* Security: Table name is validated against whitelist to prevent SQL injection (CWE-89).
|
|
3348
|
+
* Column name and definition are parameterized through prepared statements where possible.
|
|
3349
|
+
*
|
|
3350
|
+
* @param table - The table name (must be in ALLOWED_TABLE_NAMES)
|
|
3351
|
+
* @param column - The column name to ensure exists
|
|
3352
|
+
* @param definition - The SQL column definition (e.g., 'TEXT', 'INTEGER NOT NULL')
|
|
3353
|
+
* @throws {Error} If the table name is not in the whitelist
|
|
3354
|
+
*/
|
|
3355
|
+
ensureColumn(table, column, definition) {
|
|
3356
|
+
const validatedTable = validateTableName(table);
|
|
3357
|
+
const validatedColumn = validateSqlIdentifier(column, "column name");
|
|
3358
|
+
const validatedDefinition = validateColumnDefinition(definition);
|
|
3359
|
+
try {
|
|
3360
|
+
const infoStmt = this.db.prepare(`PRAGMA table_info(${validatedTable})`);
|
|
3361
|
+
const columns = infoStmt.all() ?? [];
|
|
3362
|
+
const hasColumn = columns.some((col) => col.name === validatedColumn);
|
|
3363
|
+
if (!hasColumn) {
|
|
3364
|
+
this.db.exec(
|
|
3365
|
+
`ALTER TABLE ${validatedTable} ADD COLUMN ${validatedColumn} ${validatedDefinition}`
|
|
3366
|
+
);
|
|
3367
|
+
}
|
|
3368
|
+
} catch (error) {
|
|
3369
|
+
if (!error || !(error instanceof Error) || !error.message.includes("Invalid table name")) {
|
|
3370
|
+
emitWarning(
|
|
3371
|
+
"WS-003",
|
|
3372
|
+
{
|
|
3373
|
+
taskId: "system",
|
|
3374
|
+
subsystem: "telemetry",
|
|
3375
|
+
executionPhase: "setup"
|
|
3376
|
+
},
|
|
3377
|
+
{
|
|
3378
|
+
severity: "WARN" /* WARN */,
|
|
3379
|
+
metadata: {
|
|
3380
|
+
operation: "ensure_column",
|
|
3381
|
+
table,
|
|
3382
|
+
column,
|
|
3383
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
);
|
|
3387
|
+
} else {
|
|
3388
|
+
throw error;
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
/**
|
|
3393
|
+
* Insert a model invocation record
|
|
3394
|
+
*/
|
|
3395
|
+
insertInvocation(invocation) {
|
|
3396
|
+
const stmt = this.db.prepare(`
|
|
3397
|
+
INSERT INTO model_invocations (
|
|
3398
|
+
id, timestamp, parent_workflow_id, provider_id, model_id,
|
|
3399
|
+
registry_version, tokens_in, tokens_out, time_request_sent_ms,
|
|
3400
|
+
time_first_token_ms, time_last_token_ms, time_to_first_token_ms,
|
|
3401
|
+
total_time_ms, status, error_code, request_size_bytes, agent_role,
|
|
3402
|
+
copilot_subcommand, execution_source,
|
|
3403
|
+
requested_model_id, execution_model_id, reported_model_id,
|
|
3404
|
+
backend, scope, context_selector, resolution_source,
|
|
3405
|
+
task_id, competition_id, variant_id, run_id, attempt_id, attempt_ordinal,
|
|
3406
|
+
project_id, lab_id, lab_run_id, provider_reported_cost_usd, build_calculated_cost_usd
|
|
3407
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3408
|
+
`);
|
|
3409
|
+
stmt.run(
|
|
3410
|
+
invocation.id,
|
|
3411
|
+
invocation.timestamp,
|
|
3412
|
+
invocation.parentWorkflowId ?? null,
|
|
3413
|
+
invocation.providerId,
|
|
3414
|
+
invocation.modelId,
|
|
3415
|
+
invocation.registryVersion,
|
|
3416
|
+
invocation.tokensIn ?? null,
|
|
3417
|
+
invocation.tokensOut ?? null,
|
|
3418
|
+
invocation.timeRequestSentMs,
|
|
3419
|
+
invocation.timeFirstTokenMs ?? null,
|
|
3420
|
+
invocation.timeLastTokenMs ?? null,
|
|
3421
|
+
invocation.timeToFirstTokenMs ?? null,
|
|
3422
|
+
invocation.totalTimeMs ?? null,
|
|
3423
|
+
invocation.status,
|
|
3424
|
+
invocation.errorCode ?? null,
|
|
3425
|
+
invocation.requestSizeBytes ?? null,
|
|
3426
|
+
invocation.agentRole ?? null,
|
|
3427
|
+
invocation.copilotSubcommand ?? null,
|
|
3428
|
+
invocation.executionSource ?? null,
|
|
3429
|
+
invocation.requestedModelId ?? null,
|
|
3430
|
+
invocation.executionModelId ?? null,
|
|
3431
|
+
invocation.reportedModelId ?? null,
|
|
3432
|
+
invocation.backend ?? null,
|
|
3433
|
+
invocation.scope ?? null,
|
|
3434
|
+
invocation.contextSelector ?? null,
|
|
3435
|
+
invocation.resolutionSource ?? null,
|
|
3436
|
+
invocation.taskId ?? null,
|
|
3437
|
+
invocation.competitionId ?? invocation.taskId ?? null,
|
|
3438
|
+
invocation.variantId ?? null,
|
|
3439
|
+
invocation.runId ?? null,
|
|
3440
|
+
invocation.attemptId ?? invocation.id,
|
|
3441
|
+
invocation.attemptOrdinal ?? 1,
|
|
3442
|
+
invocation.projectId ?? null,
|
|
3443
|
+
invocation.labId ?? null,
|
|
3444
|
+
invocation.labRunId ?? null,
|
|
3445
|
+
invocation.providerReportedCostUsd ?? null,
|
|
3446
|
+
invocation.buildCalculatedCostUsd ?? null
|
|
3447
|
+
);
|
|
3448
|
+
}
|
|
3449
|
+
insertUsageEvidence(evidence) {
|
|
3450
|
+
this.db.prepare(
|
|
3451
|
+
`INSERT OR IGNORE INTO usage_evidence (
|
|
3452
|
+
evidence_id, invocation_id, run_id, attempt_id, event_semantics, provenance,
|
|
3453
|
+
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
3454
|
+
reasoning_tokens, tool_tokens, cache_read_included_in_input,
|
|
3455
|
+
cache_write_included_in_input, extra_dimensions_json, availability_json,
|
|
3456
|
+
parser_version, created_at
|
|
3457
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3458
|
+
).run(
|
|
3459
|
+
evidence.evidenceId,
|
|
3460
|
+
evidence.invocationId,
|
|
3461
|
+
evidence.runId,
|
|
3462
|
+
evidence.attemptId,
|
|
3463
|
+
evidence.eventSemantics,
|
|
3464
|
+
evidence.provenance,
|
|
3465
|
+
evidence.inputTokens ?? null,
|
|
3466
|
+
evidence.outputTokens ?? null,
|
|
3467
|
+
evidence.cacheReadTokens ?? null,
|
|
3468
|
+
evidence.cacheWriteTokens ?? null,
|
|
3469
|
+
evidence.reasoningTokens ?? null,
|
|
3470
|
+
evidence.toolTokens ?? null,
|
|
3471
|
+
evidence.cacheReadIncludedInInput == null ? null : evidence.cacheReadIncludedInInput ? 1 : 0,
|
|
3472
|
+
evidence.cacheWriteIncludedInInput == null ? null : evidence.cacheWriteIncludedInInput ? 1 : 0,
|
|
3473
|
+
evidence.extraDimensionsJson ?? null,
|
|
3474
|
+
evidence.availabilityJson,
|
|
3475
|
+
evidence.parserVersion,
|
|
3476
|
+
evidence.createdAt
|
|
3477
|
+
);
|
|
3478
|
+
}
|
|
3479
|
+
getUsageEvidenceForInvocation(invocationId) {
|
|
3480
|
+
const rows = this.db.prepare(
|
|
3481
|
+
`SELECT * FROM usage_evidence
|
|
3482
|
+
WHERE invocation_id = ?
|
|
3483
|
+
ORDER BY created_at ASC, evidence_id ASC`
|
|
3484
|
+
).all(invocationId);
|
|
3485
|
+
const evidence = [];
|
|
3486
|
+
for (const row of rows) {
|
|
3487
|
+
const converted = convertRowToCamelCase(row);
|
|
3488
|
+
if (!converted) continue;
|
|
3489
|
+
evidence.push({
|
|
3490
|
+
...converted,
|
|
3491
|
+
cacheReadIncludedInInput: row.cache_read_included_in_input === null ? null : Number(row.cache_read_included_in_input) !== 0,
|
|
3492
|
+
cacheWriteIncludedInInput: row.cache_write_included_in_input === null ? null : Number(row.cache_write_included_in_input) !== 0
|
|
3493
|
+
});
|
|
3494
|
+
}
|
|
3495
|
+
if (evidence.length === 0) return null;
|
|
3496
|
+
const final = evidence.filter(
|
|
3497
|
+
(row) => ["final", "cumulative", "logical_run_aggregate"].includes(row.eventSemantics)
|
|
3498
|
+
).at(-1);
|
|
3499
|
+
if (final) return final;
|
|
3500
|
+
const base = evidence.at(-1);
|
|
3501
|
+
return {
|
|
3502
|
+
...base,
|
|
3503
|
+
inputTokens: sumNullableEvidence(evidence, "inputTokens"),
|
|
3504
|
+
outputTokens: sumNullableEvidence(evidence, "outputTokens"),
|
|
3505
|
+
cacheReadTokens: sumNullableEvidence(evidence, "cacheReadTokens"),
|
|
3506
|
+
cacheWriteTokens: sumNullableEvidence(evidence, "cacheWriteTokens"),
|
|
3507
|
+
reasoningTokens: sumNullableEvidence(evidence, "reasoningTokens"),
|
|
3508
|
+
toolTokens: sumNullableEvidence(evidence, "toolTokens"),
|
|
3509
|
+
eventSemantics: "delta"
|
|
3510
|
+
};
|
|
3511
|
+
}
|
|
3512
|
+
insertCostEvaluation(evaluation) {
|
|
3513
|
+
this.db.prepare(
|
|
3514
|
+
`INSERT OR IGNORE INTO cost_evaluations (
|
|
3515
|
+
evaluation_id, invocation_id, evidence_id, pricing_version, methodology_version,
|
|
3516
|
+
api_equivalent_cost_usd, provider_reported_cost_usd, comparison_delta_usd,
|
|
3517
|
+
currency, status, created_at
|
|
3518
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3519
|
+
).run(
|
|
3520
|
+
evaluation.evaluationId,
|
|
3521
|
+
evaluation.invocationId,
|
|
3522
|
+
evaluation.evidenceId ?? null,
|
|
3523
|
+
evaluation.pricingVersion ?? null,
|
|
3524
|
+
evaluation.methodologyVersion,
|
|
3525
|
+
evaluation.apiEquivalentCostUsd ?? null,
|
|
3526
|
+
evaluation.providerReportedCostUsd ?? null,
|
|
3527
|
+
evaluation.comparisonDeltaUsd ?? null,
|
|
3528
|
+
evaluation.currency,
|
|
3529
|
+
evaluation.status,
|
|
3530
|
+
evaluation.createdAt
|
|
3531
|
+
);
|
|
3532
|
+
}
|
|
3533
|
+
/**
|
|
3534
|
+
* Get a model invocation by ID
|
|
3535
|
+
*/
|
|
3536
|
+
getInvocation(id) {
|
|
3537
|
+
const stmt = this.db.prepare(`
|
|
3538
|
+
SELECT * FROM model_invocations WHERE id = ?
|
|
3539
|
+
`);
|
|
3540
|
+
const row = stmt.get(id);
|
|
3541
|
+
return row ? convertRowToCamelCase(row) : null;
|
|
3542
|
+
}
|
|
3543
|
+
/**
|
|
3544
|
+
* Return the most recent live invocations used by performance reporting.
|
|
3545
|
+
*
|
|
3546
|
+
* The invocation database is the canonical execution-attempt store. Results
|
|
3547
|
+
* are returned oldest-first within the requested recent window so callers can
|
|
3548
|
+
* calculate throughput and render CSV chronologically.
|
|
3549
|
+
*/
|
|
3550
|
+
getPerformanceInvocations(options) {
|
|
3551
|
+
const limit = Math.max(1, Math.trunc(options.limit));
|
|
3552
|
+
const filters = [];
|
|
3553
|
+
const params = [];
|
|
3554
|
+
filters.push(...invocationProvenanceClauses("i"));
|
|
3555
|
+
if (options.providerId) {
|
|
3556
|
+
filters.push("i.provider_id = ?");
|
|
3557
|
+
params.push(options.providerId);
|
|
3558
|
+
}
|
|
3559
|
+
if (options.variantId) {
|
|
3560
|
+
filters.push("i.variant_id = ?");
|
|
3561
|
+
params.push(options.variantId);
|
|
3562
|
+
}
|
|
3563
|
+
const where = filters.length > 0 ? `WHERE ${filters.join("\nAND ")}` : "";
|
|
3564
|
+
const rows = this.db.prepare(
|
|
3565
|
+
`SELECT i.*
|
|
3566
|
+
FROM model_invocations i
|
|
3567
|
+
${where}
|
|
3568
|
+
ORDER BY i.timestamp DESC, i.id DESC
|
|
3569
|
+
LIMIT ?`
|
|
3570
|
+
).all(...params, limit);
|
|
3571
|
+
return rows.reverse().map(
|
|
3572
|
+
(row) => convertRowToCamelCase(row)
|
|
3573
|
+
).filter((row) => row !== null);
|
|
3574
|
+
}
|
|
3575
|
+
/**
|
|
3576
|
+
* Insert a registry snapshot
|
|
3577
|
+
*/
|
|
3578
|
+
insertRegistrySnapshot(snapshot) {
|
|
3579
|
+
const stmt = this.db.prepare(`
|
|
3580
|
+
INSERT OR IGNORE INTO registry_snapshots (registry_version, created_at, json_blob)
|
|
3581
|
+
VALUES (?, ?, ?)
|
|
3582
|
+
`);
|
|
3583
|
+
stmt.run(snapshot.registryVersion, snapshot.createdAt, snapshot.jsonBlob);
|
|
3584
|
+
}
|
|
3585
|
+
/**
|
|
3586
|
+
* Get the most recently persisted registry snapshot, if any.
|
|
3587
|
+
*/
|
|
3588
|
+
getLatestRegistrySnapshot() {
|
|
3589
|
+
const snapshots = this.getRegistrySnapshots(1);
|
|
3590
|
+
return snapshots[0] ?? null;
|
|
3591
|
+
}
|
|
3592
|
+
/**
|
|
3593
|
+
* List registry snapshots newest-first.
|
|
3594
|
+
*
|
|
3595
|
+
* Used by enrichment when an invocation's stamped registry_version is a
|
|
3596
|
+
* placeholder / stale id: prefer the exact version when present, then walk
|
|
3597
|
+
* other persisted snapshots so known providers still price.
|
|
3598
|
+
*/
|
|
3599
|
+
getRegistrySnapshots(limit) {
|
|
3600
|
+
const boundedLimit = typeof limit === "number" && Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : void 0;
|
|
3601
|
+
const stmt = this.db.prepare(
|
|
3602
|
+
boundedLimit !== void 0 ? `
|
|
3603
|
+
SELECT * FROM registry_snapshots
|
|
3604
|
+
ORDER BY created_at DESC
|
|
3605
|
+
LIMIT ?
|
|
3606
|
+
` : `
|
|
3607
|
+
SELECT * FROM registry_snapshots
|
|
3608
|
+
ORDER BY created_at DESC
|
|
3609
|
+
`
|
|
3610
|
+
);
|
|
3611
|
+
const rows = (boundedLimit !== void 0 ? stmt.all(boundedLimit) : stmt.all()) ?? [];
|
|
3612
|
+
return rows.map(
|
|
3613
|
+
(row) => convertRowToCamelCase(row)
|
|
3614
|
+
).filter((snapshot) => snapshot !== null);
|
|
3615
|
+
}
|
|
3616
|
+
/**
|
|
3617
|
+
* Get the most recently persisted registry version, if any.
|
|
3618
|
+
*/
|
|
3619
|
+
getLatestRegistryVersion() {
|
|
3620
|
+
return this.getLatestRegistrySnapshot()?.registryVersion ?? null;
|
|
3621
|
+
}
|
|
3622
|
+
/**
|
|
3623
|
+
* Get a registry snapshot by version
|
|
3624
|
+
*/
|
|
3625
|
+
getRegistrySnapshot(registryVersion) {
|
|
3626
|
+
const stmt = this.db.prepare(`
|
|
3627
|
+
SELECT * FROM registry_snapshots WHERE registry_version = ?
|
|
3628
|
+
`);
|
|
3629
|
+
const row = stmt.get(registryVersion);
|
|
3630
|
+
return row ? convertRowToCamelCase(row) : null;
|
|
3631
|
+
}
|
|
3632
|
+
/**
|
|
3633
|
+
* Insert an enriched invocation record
|
|
3634
|
+
*/
|
|
3635
|
+
insertEnrichedInvocation(enriched) {
|
|
3636
|
+
const stmt = this.db.prepare(`
|
|
3637
|
+
INSERT OR REPLACE INTO enriched_invocations (
|
|
3638
|
+
id, invocation_id, computed_cost_cents, pricing_source, enriched_at, billing_mode,
|
|
3639
|
+
usage_data, cost_snapshot, pricing_version, task_id, competition_id, variant_id
|
|
3640
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3641
|
+
`);
|
|
3642
|
+
stmt.run(
|
|
3643
|
+
enriched.id,
|
|
3644
|
+
enriched.invocationId,
|
|
3645
|
+
enriched.computedCostCents ?? null,
|
|
3646
|
+
enriched.pricingSource,
|
|
3647
|
+
enriched.enrichedAt,
|
|
3648
|
+
enriched.billingMode ?? null,
|
|
3649
|
+
enriched.usageData ?? null,
|
|
3650
|
+
enriched.costSnapshot ?? null,
|
|
3651
|
+
enriched.pricingVersion ?? null,
|
|
3652
|
+
enriched.taskId ?? null,
|
|
3653
|
+
enriched.competitionId ?? null,
|
|
3654
|
+
enriched.variantId ?? null
|
|
3655
|
+
);
|
|
3656
|
+
}
|
|
3657
|
+
/**
|
|
3658
|
+
* Execute related telemetry writes atomically when the SQLite driver supports it.
|
|
3659
|
+
*/
|
|
3660
|
+
transaction(operation) {
|
|
3661
|
+
if (!this.db.transaction) {
|
|
3662
|
+
return operation();
|
|
3663
|
+
}
|
|
3664
|
+
return this.db.transaction(operation)();
|
|
3665
|
+
}
|
|
3666
|
+
/**
|
|
3667
|
+
* Get unenriched invocations
|
|
3668
|
+
*/
|
|
3669
|
+
getUnenrichedInvocations(limit = 100) {
|
|
3670
|
+
const stmt = this.db.prepare(`
|
|
3671
|
+
SELECT i.* FROM model_invocations i
|
|
3672
|
+
LEFT JOIN enriched_invocations e ON i.id = e.invocation_id
|
|
3673
|
+
WHERE e.id IS NULL
|
|
3674
|
+
ORDER BY i.timestamp ASC
|
|
3675
|
+
LIMIT ?
|
|
3676
|
+
`);
|
|
3677
|
+
const rows = stmt.all(limit) ?? [];
|
|
3678
|
+
return rows.map(
|
|
3679
|
+
(row) => convertRowToCamelCase(row)
|
|
3680
|
+
).filter((x) => x !== null);
|
|
3681
|
+
}
|
|
3682
|
+
/**
|
|
3683
|
+
* Get cost breakdown by agent role for swarm executions
|
|
3684
|
+
* Returns per-role cost totals (snapshot-first cost precedence, non-metered
|
|
3685
|
+
* rows excluded) plus how many of the role's invocations were priced.
|
|
3686
|
+
*/
|
|
3687
|
+
getRoleCostBreakdown(startTimestamp, endTimestamp) {
|
|
3688
|
+
let query = `
|
|
3689
|
+
SELECT
|
|
3690
|
+
i.agent_role as agent_role,
|
|
3691
|
+
COUNT(*) as total_invocations,
|
|
3692
|
+
COALESCE(SUM(i.tokens_in), 0) as total_tokens_in,
|
|
3693
|
+
COALESCE(SUM(i.tokens_out), 0) as total_tokens_out,
|
|
3694
|
+
COALESCE(SUM((${SNAPSHOT_FIRST_COST_CENTS_SQL})), 0) as total_cost_cents,
|
|
3695
|
+
SUM(CASE WHEN (${SNAPSHOT_FIRST_COST_CENTS_SQL}) IS NOT NULL THEN 1 ELSE 0 END) as priced_invocations
|
|
3696
|
+
FROM model_invocations i
|
|
3697
|
+
LEFT JOIN enriched_invocations e ON i.id = e.invocation_id
|
|
3698
|
+
WHERE i.agent_role IS NOT NULL
|
|
3699
|
+
`;
|
|
3700
|
+
const params = [];
|
|
3701
|
+
if (startTimestamp !== void 0) {
|
|
3702
|
+
query += " AND i.timestamp >= ?";
|
|
3703
|
+
params.push(startTimestamp);
|
|
3704
|
+
}
|
|
3705
|
+
if (endTimestamp !== void 0) {
|
|
3706
|
+
query += " AND i.timestamp <= ?";
|
|
3707
|
+
params.push(endTimestamp);
|
|
3708
|
+
}
|
|
3709
|
+
query += `
|
|
3710
|
+
GROUP BY i.agent_role
|
|
3711
|
+
ORDER BY total_cost_cents DESC, agent_role ASC
|
|
3712
|
+
`;
|
|
3713
|
+
const stmt = this.db.prepare(query);
|
|
3714
|
+
const rows = params.length > 0 ? stmt.all(...params) : stmt.all();
|
|
3715
|
+
return (rows ?? []).map(
|
|
3716
|
+
(row) => convertRowToCamelCase(row)
|
|
3717
|
+
).filter(
|
|
3718
|
+
(x) => x !== null
|
|
3719
|
+
);
|
|
3720
|
+
}
|
|
3721
|
+
/**
|
|
3722
|
+
* Return TTFT and token-throughput statistics grouped by provider/model.
|
|
3723
|
+
*
|
|
3724
|
+
* Only streaming invocations (status='success', time_to_first_token_ms IS NOT NULL)
|
|
3725
|
+
* are included. Rows with total_time_ms <= 0 or null tokens_out are excluded from
|
|
3726
|
+
* the tokens/sec calculation to avoid divide-by-zero and noise.
|
|
3727
|
+
*
|
|
3728
|
+
* Percentiles are computed in JS from a sorted slice, so they are exact for the
|
|
3729
|
+
* dataset (not approximations). Safe to call on an empty or partially-populated DB.
|
|
3730
|
+
*
|
|
3731
|
+
* @param limit - Max provider/model groups to return, ordered by invocation count desc.
|
|
3732
|
+
*/
|
|
3733
|
+
getTtftReport(limit = 20, options = {}) {
|
|
3734
|
+
return this.getTtftReportResult(limit, options).rows;
|
|
3735
|
+
}
|
|
3736
|
+
/** Return TTFT rows with explicit supplementary-query availability. */
|
|
3737
|
+
getTtftReportResult(limit = 20, options = {}) {
|
|
3738
|
+
let rows;
|
|
3739
|
+
try {
|
|
3740
|
+
const filters = ["time_to_first_token_ms IS NOT NULL", "status = 'success'"];
|
|
3741
|
+
const params = [];
|
|
3742
|
+
if (!options.includeRaw) {
|
|
3743
|
+
filters.push(...invocationAnalyticsQualityClauses());
|
|
3744
|
+
}
|
|
3745
|
+
if (options.providerId) {
|
|
3746
|
+
filters.push("provider_id = ?");
|
|
3747
|
+
params.push(options.providerId);
|
|
3748
|
+
}
|
|
3749
|
+
if (options.variantId) {
|
|
3750
|
+
filters.push("variant_id = ?");
|
|
3751
|
+
params.push(options.variantId);
|
|
3752
|
+
}
|
|
3753
|
+
const stmt = this.db.prepare(`
|
|
3754
|
+
SELECT
|
|
3755
|
+
provider_id,
|
|
3756
|
+
model_id,
|
|
3757
|
+
time_to_first_token_ms,
|
|
3758
|
+
tokens_out,
|
|
3759
|
+
total_time_ms
|
|
3760
|
+
FROM model_invocations
|
|
3761
|
+
WHERE ${filters.join("\nAND ")}
|
|
3762
|
+
ORDER BY provider_id, model_id, time_to_first_token_ms ASC
|
|
3763
|
+
`);
|
|
3764
|
+
rows = stmt.all(...params);
|
|
3765
|
+
} catch (error) {
|
|
3766
|
+
return {
|
|
3767
|
+
status: "unavailable",
|
|
3768
|
+
rows: [],
|
|
3769
|
+
unavailableReason: error instanceof Error ? error.message : String(error)
|
|
3770
|
+
};
|
|
3771
|
+
}
|
|
3772
|
+
if (rows.length === 0) return { status: "available", rows: [] };
|
|
3773
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3774
|
+
for (const row of rows) {
|
|
3775
|
+
const compatibleModelId = resolveCompatibleInvocationModelId(row.provider_id, row.model_id);
|
|
3776
|
+
const key = `${row.provider_id}\0${compatibleModelId}`;
|
|
3777
|
+
let group = groups.get(key);
|
|
3778
|
+
if (!group) {
|
|
3779
|
+
group = { providerId: row.provider_id, modelId: compatibleModelId, rows: [] };
|
|
3780
|
+
groups.set(key, group);
|
|
3781
|
+
}
|
|
3782
|
+
group.rows.push(row);
|
|
3783
|
+
}
|
|
3784
|
+
const result = [];
|
|
3785
|
+
for (const group of groups.values()) {
|
|
3786
|
+
const groupRows = group.rows;
|
|
3787
|
+
const ttfts = groupRows.map((r) => r.time_to_first_token_ms).sort((a, b) => a - b);
|
|
3788
|
+
const n = ttfts.length;
|
|
3789
|
+
if (n === 0) continue;
|
|
3790
|
+
const p50 = ttfts[Math.min(Math.floor(n * 0.5), n - 1)] ?? null;
|
|
3791
|
+
const p95 = ttfts[Math.min(Math.floor(n * 0.95), n - 1)] ?? null;
|
|
3792
|
+
const avgTtft = Math.round(ttfts.reduce((s, v) => s + v, 0) / n);
|
|
3793
|
+
const tokensSecs = groupRows.filter(
|
|
3794
|
+
(r) => r.total_time_ms !== null && r.total_time_ms > 0 && r.tokens_out !== null && r.tokens_out > 0
|
|
3795
|
+
).map((r) => r.tokens_out * 1e3 / r.total_time_ms);
|
|
3796
|
+
const avgTokensPerSec = tokensSecs.length > 0 ? Math.round(tokensSecs.reduce((s, v) => s + v, 0) / tokensSecs.length * 100) / 100 : null;
|
|
3797
|
+
result.push({
|
|
3798
|
+
providerId: group.providerId,
|
|
3799
|
+
modelId: group.modelId,
|
|
3800
|
+
streamingInvocations: n,
|
|
3801
|
+
p50TtftMs: p50,
|
|
3802
|
+
p95TtftMs: p95,
|
|
3803
|
+
avgTtftMs: avgTtft,
|
|
3804
|
+
avgTokensPerSec
|
|
3805
|
+
});
|
|
3806
|
+
}
|
|
3807
|
+
result.sort((a, b) => b.streamingInvocations - a.streamingInvocations);
|
|
3808
|
+
return { status: "available", rows: result.slice(0, limit) };
|
|
3809
|
+
}
|
|
3810
|
+
/**
|
|
3811
|
+
* Close database connection
|
|
3812
|
+
*/
|
|
3813
|
+
close() {
|
|
3814
|
+
this.db.close();
|
|
3815
|
+
}
|
|
3816
|
+
/**
|
|
3817
|
+
* Backfill daily cost aggregates from model_invocations (optional enrichment).
|
|
3818
|
+
* Idempotent via INSERT OR REPLACE; safe to call before reads or in worker cycles.
|
|
3819
|
+
*
|
|
3820
|
+
* Source universe is every matching model_invocations row; enrichment is optional
|
|
3821
|
+
* via LEFT JOIN. Numeric cost totals are stored only for fully priced groups;
|
|
3822
|
+
* incomplete groups store 0 cost so partial known-member sums are never
|
|
3823
|
+
* persisted as totals. Coverage status for reads is derived by
|
|
3824
|
+
* {@link getDailyCostAggregations}.
|
|
3825
|
+
*
|
|
3826
|
+
* @returns Number of aggregate rows upserted, or 0 on failure (worker-compatible).
|
|
3827
|
+
*/
|
|
3828
|
+
backfillDailyCostAggregations(startDate, endDate) {
|
|
3829
|
+
const result = this.tryBackfillDailyCostAggregations(startDate, endDate);
|
|
3830
|
+
return result.ok ? result.changes : 0;
|
|
3831
|
+
}
|
|
3832
|
+
/**
|
|
3833
|
+
* Internal backfill with explicit success/failure (does not launder errors to "0 changes").
|
|
3834
|
+
*/
|
|
3835
|
+
tryBackfillDailyCostAggregations(startDate, endDate) {
|
|
3836
|
+
const params = [];
|
|
3837
|
+
let whereClause = "WHERE 1=1";
|
|
3838
|
+
if (startDate) {
|
|
3839
|
+
whereClause += " AND i.timestamp >= ?";
|
|
3840
|
+
params.push(utcDateStartMs(startDate));
|
|
3841
|
+
}
|
|
3842
|
+
if (endDate) {
|
|
3843
|
+
whereClause += " AND i.timestamp < ?";
|
|
3844
|
+
params.push(utcDateStartMs(endDate) + DAY_MS);
|
|
3845
|
+
}
|
|
3846
|
+
try {
|
|
3847
|
+
const stmt = this.db.prepare(`
|
|
3848
|
+
INSERT OR REPLACE INTO cost_aggregations_daily (
|
|
3849
|
+
date,
|
|
3850
|
+
provider,
|
|
3851
|
+
model,
|
|
3852
|
+
execution_count,
|
|
3853
|
+
total_cost_usd,
|
|
3854
|
+
total_input_tokens,
|
|
3855
|
+
total_output_tokens,
|
|
3856
|
+
avg_cost_per_execution
|
|
3857
|
+
)
|
|
3858
|
+
SELECT
|
|
3859
|
+
date(i.timestamp / 1000, 'unixepoch') as date,
|
|
3860
|
+
i.provider_id as provider,
|
|
3861
|
+
i.model_id as model,
|
|
3862
|
+
COUNT(*) as execution_count,
|
|
3863
|
+
CASE
|
|
3864
|
+
WHEN COUNT(*) > 0
|
|
3865
|
+
AND COUNT(*) = SUM(
|
|
3866
|
+
CASE WHEN (${SNAPSHOT_FIRST_COST_CENTS_SQL}) IS NOT NULL THEN 1 ELSE 0 END
|
|
3867
|
+
)
|
|
3868
|
+
THEN COALESCE(SUM((${SNAPSHOT_FIRST_COST_CENTS_SQL})), 0) / 100.0
|
|
3869
|
+
ELSE 0
|
|
3870
|
+
END as total_cost_usd,
|
|
3871
|
+
COALESCE(SUM(i.tokens_in), 0) as total_input_tokens,
|
|
3872
|
+
COALESCE(SUM(i.tokens_out), 0) as total_output_tokens,
|
|
3873
|
+
CASE
|
|
3874
|
+
WHEN COUNT(*) > 0
|
|
3875
|
+
AND COUNT(*) = SUM(
|
|
3876
|
+
CASE WHEN (${SNAPSHOT_FIRST_COST_CENTS_SQL}) IS NOT NULL THEN 1 ELSE 0 END
|
|
3877
|
+
)
|
|
3878
|
+
THEN COALESCE(SUM((${SNAPSHOT_FIRST_COST_CENTS_SQL})), 0) / 100.0 / COUNT(*)
|
|
3879
|
+
ELSE 0
|
|
3880
|
+
END as avg_cost_per_execution
|
|
3881
|
+
FROM model_invocations i
|
|
3882
|
+
LEFT JOIN enriched_invocations e ON e.invocation_id = i.id
|
|
3883
|
+
${whereClause}
|
|
3884
|
+
GROUP BY date, provider, model
|
|
3885
|
+
`);
|
|
3886
|
+
const result = params.length > 0 ? stmt.run(...params) : stmt.run();
|
|
3887
|
+
return { ok: true, changes: result.changes ?? 0 };
|
|
3888
|
+
} catch (error) {
|
|
3889
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3890
|
+
emitWarning(
|
|
3891
|
+
"WS-003",
|
|
3892
|
+
{
|
|
3893
|
+
taskId: "system",
|
|
3894
|
+
subsystem: "telemetry",
|
|
3895
|
+
executionPhase: "execution"
|
|
3896
|
+
},
|
|
3897
|
+
{
|
|
3898
|
+
severity: "WARN" /* WARN */,
|
|
3899
|
+
metadata: {
|
|
3900
|
+
operation: "backfill_daily_cost_aggregations",
|
|
3901
|
+
error: message,
|
|
3902
|
+
startDate,
|
|
3903
|
+
endDate
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
);
|
|
3907
|
+
return { ok: false, error: message };
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
/**
|
|
3911
|
+
* Get daily cost aggregations for a specified date range.
|
|
3912
|
+
*
|
|
3913
|
+
* Status-bearing: numeric totals/averages exist only when every included
|
|
3914
|
+
* invocation has authoritative snapshot-first cost evidence. Mixed or
|
|
3915
|
+
* all-unpriced evidence is `incomplete` (counts retained, costs null).
|
|
3916
|
+
* Query/backfill failure is `error` (no totals). Empty complete means no data.
|
|
3917
|
+
*
|
|
3918
|
+
* Source universe is every matching model_invocations row (optional enrichment
|
|
3919
|
+
* via LEFT JOIN). Live and legacy tables are composed at the storage-native
|
|
3920
|
+
* date+provider+model key first: live replaces only its exact native key;
|
|
3921
|
+
* legacy-only native keys remain. Only then are merged native rows aggregated
|
|
3922
|
+
* to the requested date/provider/model grouping. A group total is numeric only
|
|
3923
|
+
* when every constituent native row is complete (no partial subtotals).
|
|
3924
|
+
* Ordering is preserved (date DESC, then provider/model ASC when grouped).
|
|
3925
|
+
*/
|
|
3926
|
+
getDailyCostAggregations(startDate, endDate, groupBy) {
|
|
3927
|
+
const backfill = this.tryBackfillDailyCostAggregations(startDate, endDate);
|
|
3928
|
+
if (!backfill.ok) {
|
|
3929
|
+
return {
|
|
3930
|
+
status: "error",
|
|
3931
|
+
rows: [],
|
|
3932
|
+
executionCount: 0,
|
|
3933
|
+
pricedCount: 0,
|
|
3934
|
+
error: backfill.error,
|
|
3935
|
+
detail: "backfill_daily_cost_aggregations failed"
|
|
3936
|
+
};
|
|
3937
|
+
}
|
|
3938
|
+
try {
|
|
3939
|
+
const liveNative = this.queryLiveDailyCostCoverage(startDate, endDate);
|
|
3940
|
+
const importedNative = this.queryImportedDailyCostCoverage(startDate, endDate);
|
|
3941
|
+
const combinedLiveNative = this.combineLiveCoverage(liveNative, importedNative);
|
|
3942
|
+
const legacyNative = this.queryPreseededDailyCostAggregationsNative(startDate, endDate);
|
|
3943
|
+
if (combinedLiveNative.length === 0) {
|
|
3944
|
+
const rows2 = this.aggregateNativeDailyCostRows(legacyNative, groupBy);
|
|
3945
|
+
return this.buildDailyCostResultFromRows(rows2);
|
|
3946
|
+
}
|
|
3947
|
+
if (legacyNative.length === 0) {
|
|
3948
|
+
const rows2 = this.aggregateNativeDailyCostRows(
|
|
3949
|
+
this.buildDailyCostRowsFromLive(combinedLiveNative),
|
|
3950
|
+
groupBy
|
|
3951
|
+
);
|
|
3952
|
+
return this.buildDailyCostResultFromRows(rows2);
|
|
3953
|
+
}
|
|
3954
|
+
const mergedNative = this.mergeLiveAndLegacyAtNativeKey(combinedLiveNative, legacyNative);
|
|
3955
|
+
const rows = this.aggregateNativeDailyCostRows(mergedNative, groupBy);
|
|
3956
|
+
return this.buildDailyCostResultFromRows(rows);
|
|
3957
|
+
} catch (error) {
|
|
3958
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3959
|
+
emitWarning(
|
|
3960
|
+
"WS-003",
|
|
3961
|
+
{
|
|
3962
|
+
taskId: "system",
|
|
3963
|
+
subsystem: "telemetry",
|
|
3964
|
+
executionPhase: "execution"
|
|
3965
|
+
},
|
|
3966
|
+
{
|
|
3967
|
+
severity: "WARN" /* WARN */,
|
|
3968
|
+
metadata: {
|
|
3969
|
+
operation: "query_daily_cost_aggregations",
|
|
3970
|
+
error: message,
|
|
3971
|
+
startDate,
|
|
3972
|
+
endDate
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3975
|
+
);
|
|
3976
|
+
return {
|
|
3977
|
+
status: "error",
|
|
3978
|
+
rows: [],
|
|
3979
|
+
executionCount: 0,
|
|
3980
|
+
pricedCount: 0,
|
|
3981
|
+
error: message,
|
|
3982
|
+
detail: "query_daily_cost_aggregations failed"
|
|
3983
|
+
};
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
queryImportedDailyCostCoverage(startDate, endDate) {
|
|
3987
|
+
if (this.portabilityDbDir && !this.projectSyncId) {
|
|
3988
|
+
return [];
|
|
3989
|
+
}
|
|
3990
|
+
const portabilityPath = [
|
|
3991
|
+
this.portabilityDbDir ? path5.join(this.portabilityDbDir, "portability.db") : void 0,
|
|
3992
|
+
path5.join(path5.dirname(this.dbPath), "db", "portability.db"),
|
|
3993
|
+
path5.join(path5.dirname(this.dbPath), "portability.db")
|
|
3994
|
+
].find((candidate) => Boolean(candidate && existsSync2(candidate)));
|
|
3995
|
+
if (!portabilityPath) return [];
|
|
3996
|
+
const imported = new sqlite_wrapper_default(portabilityPath);
|
|
3997
|
+
try {
|
|
3998
|
+
applySqlitePragmas(imported, { foreignKeys: true });
|
|
3999
|
+
const rows = loadImportedCostRecords(
|
|
4000
|
+
imported,
|
|
4001
|
+
this.db,
|
|
4002
|
+
this.projectSyncId,
|
|
4003
|
+
startDate ? utcDateStartMs(startDate) : void 0,
|
|
4004
|
+
endDate ? utcDateStartMs(endDate) + DAY_MS : void 0
|
|
4005
|
+
);
|
|
4006
|
+
const invocations = /* @__PURE__ */ new Map();
|
|
4007
|
+
const usage = /* @__PURE__ */ new Map();
|
|
4008
|
+
const evaluations = /* @__PURE__ */ new Map();
|
|
4009
|
+
for (const row of rows) {
|
|
4010
|
+
const payload = parseJsonWithContext(
|
|
4011
|
+
row.payloadJson,
|
|
4012
|
+
"portable cost payload"
|
|
4013
|
+
);
|
|
4014
|
+
const invocationId = typeof payload.invocationId === "string" ? payload.invocationId : void 0;
|
|
4015
|
+
if (!invocationId) continue;
|
|
4016
|
+
if (row.recordType === "invocation") invocations.set(invocationId, payload);
|
|
4017
|
+
if (row.recordType === "usage") {
|
|
4018
|
+
const current = usage.get(invocationId) ?? [];
|
|
4019
|
+
current.push(payload);
|
|
4020
|
+
usage.set(invocationId, current);
|
|
4021
|
+
}
|
|
4022
|
+
if (row.recordType === "costEvaluation") {
|
|
4023
|
+
const current = evaluations.get(invocationId);
|
|
4024
|
+
if (!current || Number(payload.createdAt ?? 0) >= Number(current.createdAt ?? 0)) {
|
|
4025
|
+
evaluations.set(invocationId, payload);
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
const native = /* @__PURE__ */ new Map();
|
|
4030
|
+
for (const [invocationId, invocation] of invocations) {
|
|
4031
|
+
const timestamp = Number(invocation.timestamp);
|
|
4032
|
+
if (!Number.isFinite(timestamp)) continue;
|
|
4033
|
+
const date = new Date(timestamp).toISOString().slice(0, 10);
|
|
4034
|
+
if (startDate && date < startDate || endDate && date > endDate) continue;
|
|
4035
|
+
const provider = String(invocation.provider ?? "unknown");
|
|
4036
|
+
const model = String(invocation.model ?? "unknown");
|
|
4037
|
+
const key = `${date}\0${provider}\0${model}`;
|
|
4038
|
+
const usagePayload = reducePortableUsage(usage.get(invocationId));
|
|
4039
|
+
const evaluation = evaluations.get(invocationId);
|
|
4040
|
+
const apiCost = typeof evaluation?.apiEquivalentCostUsd === "number" ? evaluation.apiEquivalentCostUsd : typeof invocation.inlineApiEquivalentCostUsd === "number" ? invocation.inlineApiEquivalentCostUsd : null;
|
|
4041
|
+
const inputTokens = typeof usagePayload?.inputTokens === "number" ? usagePayload.inputTokens : typeof invocation.inlineTokensIn === "number" ? invocation.inlineTokensIn : 0;
|
|
4042
|
+
const outputTokens = typeof usagePayload?.outputTokens === "number" ? usagePayload.outputTokens : typeof invocation.inlineTokensOut === "number" ? invocation.inlineTokensOut : 0;
|
|
4043
|
+
const existing = native.get(key) ?? {
|
|
4044
|
+
date,
|
|
4045
|
+
provider,
|
|
4046
|
+
model,
|
|
4047
|
+
execution_count: 0,
|
|
4048
|
+
priced_count: 0,
|
|
4049
|
+
total_cost_cents: 0,
|
|
4050
|
+
total_input_tokens: 0,
|
|
4051
|
+
total_output_tokens: 0
|
|
4052
|
+
};
|
|
4053
|
+
existing.execution_count += 1;
|
|
4054
|
+
existing.total_input_tokens += inputTokens;
|
|
4055
|
+
existing.total_output_tokens += outputTokens;
|
|
4056
|
+
if (apiCost !== null) {
|
|
4057
|
+
existing.priced_count += 1;
|
|
4058
|
+
if (existing.total_cost_cents !== null) {
|
|
4059
|
+
existing.total_cost_cents += apiCost * 100;
|
|
4060
|
+
}
|
|
4061
|
+
} else {
|
|
4062
|
+
existing.total_cost_cents = null;
|
|
4063
|
+
}
|
|
4064
|
+
native.set(key, existing);
|
|
4065
|
+
}
|
|
4066
|
+
return [...native.values()];
|
|
4067
|
+
} finally {
|
|
4068
|
+
imported.close();
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
combineLiveCoverage(local, imported) {
|
|
4072
|
+
const combined = /* @__PURE__ */ new Map();
|
|
4073
|
+
for (const row of [...local, ...imported]) {
|
|
4074
|
+
const key = `${row.date}\0${row.provider}\0${row.model}`;
|
|
4075
|
+
const current = combined.get(key);
|
|
4076
|
+
if (!current) {
|
|
4077
|
+
combined.set(key, { ...row });
|
|
4078
|
+
continue;
|
|
4079
|
+
}
|
|
4080
|
+
current.execution_count += row.execution_count;
|
|
4081
|
+
current.priced_count += row.priced_count;
|
|
4082
|
+
current.total_input_tokens += row.total_input_tokens;
|
|
4083
|
+
current.total_output_tokens += row.total_output_tokens;
|
|
4084
|
+
current.total_cost_cents = current.total_cost_cents === null || row.total_cost_cents === null ? null : current.total_cost_cents + row.total_cost_cents;
|
|
4085
|
+
}
|
|
4086
|
+
return [...combined.values()];
|
|
4087
|
+
}
|
|
4088
|
+
/**
|
|
4089
|
+
* Storage-native merge identity: date + provider + model.
|
|
4090
|
+
*/
|
|
4091
|
+
nativeDailyCostKey(row) {
|
|
4092
|
+
return `${row.date}\0${row.provider ?? ""}\0${row.model ?? ""}`;
|
|
4093
|
+
}
|
|
4094
|
+
/**
|
|
4095
|
+
* Stable key for daily aggregation under the requested grouping.
|
|
4096
|
+
*/
|
|
4097
|
+
dailyCostAggregationKey(row, groupBy) {
|
|
4098
|
+
if (groupBy === "provider") {
|
|
4099
|
+
return `${row.date}\0${row.provider ?? ""}`;
|
|
4100
|
+
}
|
|
4101
|
+
if (groupBy === "model") {
|
|
4102
|
+
return `${row.date}\0${row.model ?? ""}`;
|
|
4103
|
+
}
|
|
4104
|
+
return row.date;
|
|
4105
|
+
}
|
|
4106
|
+
/**
|
|
4107
|
+
* Ordering: date DESC, then provider/model ASC when grouped (matches SQL ORDER BY).
|
|
4108
|
+
* At native resolution, order by date DESC, provider ASC, model ASC.
|
|
4109
|
+
*/
|
|
4110
|
+
compareDailyCostAggregationRows(a, b, groupBy) {
|
|
4111
|
+
if (a.date !== b.date) {
|
|
4112
|
+
return a.date < b.date ? 1 : -1;
|
|
4113
|
+
}
|
|
4114
|
+
if (groupBy === "provider" || groupBy === "native") {
|
|
4115
|
+
const ap = a.provider ?? "";
|
|
4116
|
+
const bp = b.provider ?? "";
|
|
4117
|
+
if (ap !== bp) {
|
|
4118
|
+
return ap < bp ? -1 : 1;
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
if (groupBy === "model" || groupBy === "native") {
|
|
4122
|
+
const am = a.model ?? "";
|
|
4123
|
+
const bm = b.model ?? "";
|
|
4124
|
+
if (am !== bm) {
|
|
4125
|
+
return am < bm ? -1 : 1;
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
return 0;
|
|
4129
|
+
}
|
|
4130
|
+
/**
|
|
4131
|
+
* Live coverage at native date+provider+model resolution.
|
|
4132
|
+
* Source universe: model_invocations; enrichment optional via LEFT JOIN.
|
|
4133
|
+
*/
|
|
4134
|
+
queryLiveDailyCostCoverage(startDate, endDate) {
|
|
4135
|
+
const params = [];
|
|
4136
|
+
let whereClause = "WHERE 1=1";
|
|
4137
|
+
if (startDate) {
|
|
4138
|
+
whereClause += " AND i.timestamp >= ?";
|
|
4139
|
+
params.push(utcDateStartMs(startDate));
|
|
4140
|
+
}
|
|
4141
|
+
if (endDate) {
|
|
4142
|
+
whereClause += " AND i.timestamp < ?";
|
|
4143
|
+
params.push(utcDateStartMs(endDate) + DAY_MS);
|
|
4144
|
+
}
|
|
4145
|
+
const query = `
|
|
4146
|
+
SELECT
|
|
4147
|
+
date(i.timestamp / 1000, 'unixepoch') as date,
|
|
4148
|
+
i.provider_id as provider,
|
|
4149
|
+
i.model_id as model,
|
|
4150
|
+
COUNT(*) as execution_count,
|
|
4151
|
+
SUM(CASE WHEN (${SNAPSHOT_FIRST_COST_CENTS_SQL}) IS NOT NULL THEN 1 ELSE 0 END) as priced_count,
|
|
4152
|
+
SUM(CASE WHEN ${NON_METERED_COST_SQL} THEN 1 ELSE 0 END) as non_metered_count,
|
|
4153
|
+
SUM((${SNAPSHOT_FIRST_COST_CENTS_SQL})) as total_cost_cents,
|
|
4154
|
+
COALESCE(SUM(i.tokens_in), 0) as total_input_tokens,
|
|
4155
|
+
COALESCE(SUM(i.tokens_out), 0) as total_output_tokens
|
|
4156
|
+
FROM model_invocations i
|
|
4157
|
+
LEFT JOIN enriched_invocations e ON e.invocation_id = i.id
|
|
4158
|
+
${whereClause}
|
|
4159
|
+
GROUP BY date, i.provider_id, i.model_id
|
|
4160
|
+
ORDER BY date DESC, provider ASC, model ASC
|
|
4161
|
+
`;
|
|
4162
|
+
const stmt = this.db.prepare(query);
|
|
4163
|
+
const rows = params.length > 0 ? stmt.all(...params) : stmt.all();
|
|
4164
|
+
return rows.map((row) => this.normalizeLiveDailyCostCoverageRow(row));
|
|
4165
|
+
}
|
|
4166
|
+
normalizeLiveDailyCostCoverageRow(row) {
|
|
4167
|
+
if (row === null || typeof row !== "object") {
|
|
4168
|
+
return {
|
|
4169
|
+
date: "",
|
|
4170
|
+
provider: "",
|
|
4171
|
+
model: "",
|
|
4172
|
+
execution_count: 0,
|
|
4173
|
+
priced_count: 0,
|
|
4174
|
+
total_cost_cents: null,
|
|
4175
|
+
total_input_tokens: 0,
|
|
4176
|
+
total_output_tokens: 0
|
|
4177
|
+
};
|
|
4178
|
+
}
|
|
4179
|
+
const dateValue = Reflect.get(row, "date");
|
|
4180
|
+
const providerValue = Reflect.get(row, "provider");
|
|
4181
|
+
const modelValue = Reflect.get(row, "model");
|
|
4182
|
+
const totalCostCentsValue = Reflect.get(row, "total_cost_cents");
|
|
4183
|
+
const totalCostCents = totalCostCentsValue == null ? null : Number(totalCostCentsValue);
|
|
4184
|
+
return {
|
|
4185
|
+
date: typeof dateValue === "string" ? dateValue : String(dateValue ?? ""),
|
|
4186
|
+
provider: typeof providerValue === "string" ? providerValue : String(providerValue ?? ""),
|
|
4187
|
+
model: typeof modelValue === "string" ? modelValue : String(modelValue ?? ""),
|
|
4188
|
+
execution_count: Number(Reflect.get(row, "execution_count")) || 0,
|
|
4189
|
+
priced_count: Number(Reflect.get(row, "priced_count")) || 0,
|
|
4190
|
+
non_metered_count: Number(Reflect.get(row, "non_metered_count")) || 0,
|
|
4191
|
+
total_cost_cents: totalCostCents === null || Number.isNaN(totalCostCents) ? null : totalCostCents,
|
|
4192
|
+
total_input_tokens: Number(Reflect.get(row, "total_input_tokens")) || 0,
|
|
4193
|
+
total_output_tokens: Number(Reflect.get(row, "total_output_tokens")) || 0
|
|
4194
|
+
};
|
|
4195
|
+
}
|
|
4196
|
+
buildDailyCostRowsFromLive(live) {
|
|
4197
|
+
return live.map((row) => {
|
|
4198
|
+
const execCount = Number(row.execution_count) || 0;
|
|
4199
|
+
const priced = Number(row.priced_count) || 0;
|
|
4200
|
+
const rowComplete = execCount > 0 && priced + (row.non_metered_count ?? 0) === execCount;
|
|
4201
|
+
const totalCost = rowComplete ? (Number(row.total_cost_cents) || 0) / 100 : null;
|
|
4202
|
+
const avgCostPerExecution = rowComplete && execCount > 0 && totalCost !== null ? totalCost / execCount : null;
|
|
4203
|
+
return {
|
|
4204
|
+
date: row.date,
|
|
4205
|
+
provider: row.provider,
|
|
4206
|
+
model: row.model,
|
|
4207
|
+
executionCount: execCount,
|
|
4208
|
+
pricedCount: priced,
|
|
4209
|
+
totalTokensIn: Number(row.total_input_tokens) || 0,
|
|
4210
|
+
totalTokensOut: Number(row.total_output_tokens) || 0,
|
|
4211
|
+
totalCost,
|
|
4212
|
+
avgCostPerExecution,
|
|
4213
|
+
status: rowComplete ? "complete" : "incomplete"
|
|
4214
|
+
};
|
|
4215
|
+
});
|
|
4216
|
+
}
|
|
4217
|
+
buildDailyCostResultFromRows(rows) {
|
|
4218
|
+
let executionCount = 0;
|
|
4219
|
+
let pricedCount = 0;
|
|
4220
|
+
let anyIncomplete = false;
|
|
4221
|
+
for (const row of rows) {
|
|
4222
|
+
executionCount += row.executionCount;
|
|
4223
|
+
pricedCount += row.pricedCount;
|
|
4224
|
+
if (row.status === "incomplete") {
|
|
4225
|
+
anyIncomplete = true;
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
return {
|
|
4229
|
+
status: anyIncomplete ? "incomplete" : "complete",
|
|
4230
|
+
rows,
|
|
4231
|
+
executionCount,
|
|
4232
|
+
pricedCount,
|
|
4233
|
+
...anyIncomplete ? { detail: `incomplete cost evidence (${pricedCount}/${executionCount} priced)` } : {}
|
|
4234
|
+
};
|
|
4235
|
+
}
|
|
4236
|
+
/**
|
|
4237
|
+
* Compose live and legacy rows at storage-native date+provider+model keys.
|
|
4238
|
+
* Live is authoritative for its exact native key only; legacy-only keys remain.
|
|
4239
|
+
*/
|
|
4240
|
+
mergeLiveAndLegacyAtNativeKey(live, legacyRows) {
|
|
4241
|
+
const liveRows = this.buildDailyCostRowsFromLive(live);
|
|
4242
|
+
const liveKeys = new Set(liveRows.map((row) => this.nativeDailyCostKey(row)));
|
|
4243
|
+
const legacyOnly = legacyRows.filter((row) => !liveKeys.has(this.nativeDailyCostKey(row)));
|
|
4244
|
+
return [...liveRows, ...legacyOnly];
|
|
4245
|
+
}
|
|
4246
|
+
/**
|
|
4247
|
+
* Aggregate storage-native rows to the requested date/provider/model grouping.
|
|
4248
|
+
* A group total/average is numeric only when every constituent native row is
|
|
4249
|
+
* complete; partial subtotals are never exposed.
|
|
4250
|
+
*/
|
|
4251
|
+
aggregateNativeDailyCostRows(nativeRows, groupBy) {
|
|
4252
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4253
|
+
for (const row of nativeRows) {
|
|
4254
|
+
const key = this.dailyCostAggregationKey(row, groupBy);
|
|
4255
|
+
let group = groups.get(key);
|
|
4256
|
+
if (!group) {
|
|
4257
|
+
group = {
|
|
4258
|
+
date: row.date,
|
|
4259
|
+
...groupBy === "provider" ? { provider: row.provider } : {},
|
|
4260
|
+
...groupBy === "model" ? { model: row.model } : {},
|
|
4261
|
+
members: []
|
|
4262
|
+
};
|
|
4263
|
+
groups.set(key, group);
|
|
4264
|
+
}
|
|
4265
|
+
group.members.push(row);
|
|
4266
|
+
}
|
|
4267
|
+
const aggregated = [];
|
|
4268
|
+
for (const group of groups.values()) {
|
|
4269
|
+
let executionCount = 0;
|
|
4270
|
+
let pricedCount = 0;
|
|
4271
|
+
let totalTokensIn = 0;
|
|
4272
|
+
let totalTokensOut = 0;
|
|
4273
|
+
let allComplete = true;
|
|
4274
|
+
let totalCostSum = 0;
|
|
4275
|
+
for (const member of group.members) {
|
|
4276
|
+
executionCount += member.executionCount;
|
|
4277
|
+
pricedCount += member.pricedCount;
|
|
4278
|
+
totalTokensIn += member.totalTokensIn;
|
|
4279
|
+
totalTokensOut += member.totalTokensOut;
|
|
4280
|
+
if (member.status !== "complete" || member.totalCost === null) {
|
|
4281
|
+
allComplete = false;
|
|
4282
|
+
} else {
|
|
4283
|
+
totalCostSum += member.totalCost;
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
const complete = allComplete && executionCount > 0;
|
|
4287
|
+
const totalCost = complete ? totalCostSum : null;
|
|
4288
|
+
const avgCostPerExecution = complete && executionCount > 0 && totalCost !== null ? totalCost / executionCount : null;
|
|
4289
|
+
aggregated.push({
|
|
4290
|
+
date: group.date,
|
|
4291
|
+
provider: group.provider,
|
|
4292
|
+
model: group.model,
|
|
4293
|
+
executionCount,
|
|
4294
|
+
pricedCount,
|
|
4295
|
+
totalTokensIn,
|
|
4296
|
+
totalTokensOut,
|
|
4297
|
+
totalCost,
|
|
4298
|
+
avgCostPerExecution,
|
|
4299
|
+
status: complete ? "complete" : "incomplete"
|
|
4300
|
+
});
|
|
4301
|
+
}
|
|
4302
|
+
return aggregated.sort((a, b) => this.compareDailyCostAggregationRows(a, b, groupBy));
|
|
4303
|
+
}
|
|
4304
|
+
/**
|
|
4305
|
+
* Pre-seeded cost_aggregations_daily fixtures at storage-native resolution
|
|
4306
|
+
* (date, provider, model). Treated as complete; pricedCount equals
|
|
4307
|
+
* executionCount. Callers that also have live coverage must merge at the
|
|
4308
|
+
* native key so live remains authoritative for exact collisions only.
|
|
4309
|
+
*/
|
|
4310
|
+
queryPreseededDailyCostAggregationsNative(startDate, endDate) {
|
|
4311
|
+
const params = [];
|
|
4312
|
+
let whereClause = " WHERE 1=1";
|
|
4313
|
+
if (startDate) {
|
|
4314
|
+
whereClause += " AND date >= ?";
|
|
4315
|
+
params.push(startDate);
|
|
4316
|
+
}
|
|
4317
|
+
if (endDate) {
|
|
4318
|
+
whereClause += " AND date <= ?";
|
|
4319
|
+
params.push(endDate);
|
|
4320
|
+
}
|
|
4321
|
+
const query = `
|
|
4322
|
+
SELECT
|
|
4323
|
+
date,
|
|
4324
|
+
provider,
|
|
4325
|
+
model,
|
|
4326
|
+
execution_count,
|
|
4327
|
+
total_cost_usd,
|
|
4328
|
+
total_input_tokens,
|
|
4329
|
+
total_output_tokens,
|
|
4330
|
+
avg_cost_per_execution
|
|
4331
|
+
FROM cost_aggregations_daily
|
|
4332
|
+
${whereClause}
|
|
4333
|
+
ORDER BY date DESC, provider ASC, model ASC
|
|
4334
|
+
`;
|
|
4335
|
+
const stmt = this.db.prepare(query);
|
|
4336
|
+
const rawRows = params.length > 0 ? stmt.all(...params) : stmt.all();
|
|
4337
|
+
return rawRows.map((raw) => this.normalizePreseededDailyCostAggregateRow(raw));
|
|
4338
|
+
}
|
|
4339
|
+
normalizePreseededDailyCostAggregateRow(raw) {
|
|
4340
|
+
if (raw === null || typeof raw !== "object") {
|
|
4341
|
+
return {
|
|
4342
|
+
date: "",
|
|
4343
|
+
executionCount: 0,
|
|
4344
|
+
pricedCount: 0,
|
|
4345
|
+
totalTokensIn: 0,
|
|
4346
|
+
totalTokensOut: 0,
|
|
4347
|
+
totalCost: 0,
|
|
4348
|
+
avgCostPerExecution: 0,
|
|
4349
|
+
status: "complete"
|
|
4350
|
+
};
|
|
4351
|
+
}
|
|
4352
|
+
const dateValue = Reflect.get(raw, "date");
|
|
4353
|
+
const providerValue = Reflect.get(raw, "provider");
|
|
4354
|
+
const modelValue = Reflect.get(raw, "model");
|
|
4355
|
+
const avgValue = Reflect.get(raw, "avg_cost_per_execution");
|
|
4356
|
+
const execCount = Number(Reflect.get(raw, "execution_count")) || 0;
|
|
4357
|
+
const totalCost = Number(Reflect.get(raw, "total_cost_usd")) || 0;
|
|
4358
|
+
const avgFromRow = avgValue === null || avgValue === void 0 ? null : Number(avgValue);
|
|
4359
|
+
const avgCostPerExecution = avgFromRow !== null && !Number.isNaN(avgFromRow) ? avgFromRow : execCount > 0 ? totalCost / execCount : 0;
|
|
4360
|
+
const provider = typeof providerValue === "string" ? providerValue : void 0;
|
|
4361
|
+
const model = typeof modelValue === "string" ? modelValue : void 0;
|
|
4362
|
+
return {
|
|
4363
|
+
date: typeof dateValue === "string" ? dateValue : String(dateValue ?? ""),
|
|
4364
|
+
...provider !== void 0 ? { provider } : {},
|
|
4365
|
+
...model !== void 0 ? { model } : {},
|
|
4366
|
+
executionCount: execCount,
|
|
4367
|
+
pricedCount: execCount,
|
|
4368
|
+
totalTokensIn: Number(Reflect.get(raw, "total_input_tokens")) || 0,
|
|
4369
|
+
totalTokensOut: Number(Reflect.get(raw, "total_output_tokens")) || 0,
|
|
4370
|
+
totalCost,
|
|
4371
|
+
avgCostPerExecution,
|
|
4372
|
+
status: "complete"
|
|
4373
|
+
};
|
|
4374
|
+
}
|
|
4375
|
+
/**
|
|
4376
|
+
* Determine if telemetry contains non-metered billing records
|
|
4377
|
+
*/
|
|
4378
|
+
hasNonMeteredBilling() {
|
|
4379
|
+
try {
|
|
4380
|
+
const stmt = this.db.prepare(`
|
|
4381
|
+
SELECT 1 FROM enriched_invocations
|
|
4382
|
+
WHERE billing_mode IS NOT NULL AND billing_mode != 'metered'
|
|
4383
|
+
LIMIT 1
|
|
4384
|
+
`);
|
|
4385
|
+
const row = stmt.get();
|
|
4386
|
+
return Boolean(row);
|
|
4387
|
+
} catch (error) {
|
|
4388
|
+
emitWarning(
|
|
4389
|
+
"WS-003",
|
|
4390
|
+
{
|
|
4391
|
+
taskId: "system",
|
|
4392
|
+
subsystem: "telemetry",
|
|
4393
|
+
executionPhase: "execution"
|
|
4394
|
+
},
|
|
4395
|
+
{
|
|
4396
|
+
severity: "WARN" /* WARN */,
|
|
4397
|
+
metadata: {
|
|
4398
|
+
operation: "query_non_metered_billing",
|
|
4399
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4402
|
+
);
|
|
4403
|
+
return false;
|
|
4404
|
+
}
|
|
4405
|
+
}
|
|
4406
|
+
// ============================================================================
|
|
4407
|
+
// HYBRID STORAGE: Cost Tracking with Tokens + Snapshot
|
|
4408
|
+
// ============================================================================
|
|
4409
|
+
/**
|
|
4410
|
+
* Record an execution cost with hybrid storage (tokens + cost snapshot)
|
|
4411
|
+
* Stores both raw usage data (immutable) and cost snapshot (historical accuracy)
|
|
4412
|
+
*/
|
|
4413
|
+
recordExecutionCost(record) {
|
|
4414
|
+
const stmt = this.db.prepare(`
|
|
4415
|
+
INSERT OR REPLACE INTO enriched_invocations (
|
|
4416
|
+
id,
|
|
4417
|
+
invocation_id,
|
|
4418
|
+
computed_cost_cents,
|
|
4419
|
+
pricing_source,
|
|
4420
|
+
enriched_at,
|
|
4421
|
+
billing_mode,
|
|
4422
|
+
usage_data,
|
|
4423
|
+
cost_snapshot,
|
|
4424
|
+
pricing_version,
|
|
4425
|
+
task_id,
|
|
4426
|
+
competition_id,
|
|
4427
|
+
variant_id
|
|
4428
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4429
|
+
`);
|
|
4430
|
+
const enrichedId = `enrich_${Date.now()}_${randomBytes(8).toString("hex")}`;
|
|
4431
|
+
const enrichedAt = record.timestamp.getTime();
|
|
4432
|
+
stmt.run(
|
|
4433
|
+
enrichedId,
|
|
4434
|
+
record.executionId,
|
|
4435
|
+
record.costSnapshot.totalCostCents,
|
|
4436
|
+
record.pricingSource,
|
|
4437
|
+
enrichedAt,
|
|
4438
|
+
"metered",
|
|
4439
|
+
// Default to metered for new records
|
|
4440
|
+
JSON.stringify(record.usage),
|
|
4441
|
+
JSON.stringify(record.costSnapshot),
|
|
4442
|
+
record.pricingVersion,
|
|
4443
|
+
record.taskId ?? null,
|
|
4444
|
+
record.competitionId ?? null,
|
|
4445
|
+
record.variantId ?? null
|
|
4446
|
+
);
|
|
4447
|
+
}
|
|
4448
|
+
/**
|
|
4449
|
+
* Get execution cost record by execution ID
|
|
4450
|
+
* Reconstructs full ExecutionCostRecord from hybrid storage
|
|
4451
|
+
*/
|
|
4452
|
+
getExecutionCost(executionId) {
|
|
4453
|
+
const stmt = this.db.prepare(`
|
|
4454
|
+
SELECT ${EXECUTION_COST_COLUMNS}
|
|
4455
|
+
FROM enriched_invocations e
|
|
4456
|
+
JOIN model_invocations i ON e.invocation_id = i.id
|
|
4457
|
+
WHERE e.invocation_id = ?
|
|
4458
|
+
`);
|
|
4459
|
+
const row = stmt.get(executionId);
|
|
4460
|
+
if (!row) {
|
|
4461
|
+
return null;
|
|
4462
|
+
}
|
|
4463
|
+
const usage = row.usage_data ? parseExecutionCostUsage(row.usage_data) : null;
|
|
4464
|
+
const resolvedCostSnapshot = resolveExecutionCostSnapshot(row);
|
|
4465
|
+
if (!usage || !resolvedCostSnapshot) {
|
|
4466
|
+
return null;
|
|
4467
|
+
}
|
|
4468
|
+
return projectExecutionCostRecord(row, usage, resolvedCostSnapshot);
|
|
4469
|
+
}
|
|
4470
|
+
/**
|
|
4471
|
+
* Recalculate cost for an execution using current pricing
|
|
4472
|
+
* Requires calculator function to be passed in to avoid circular dependency
|
|
4473
|
+
*
|
|
4474
|
+
* @param executionId - Execution ID to recalculate
|
|
4475
|
+
* @param calculateCost - Cost calculation function (provider, model, usage) => cost in cents
|
|
4476
|
+
* @returns New cost in cents, or null if execution not found or missing usage data
|
|
4477
|
+
*/
|
|
4478
|
+
recalculateCost(executionId, calculateCost) {
|
|
4479
|
+
const record = this.getExecutionCost(executionId);
|
|
4480
|
+
if (!record) {
|
|
4481
|
+
return null;
|
|
4482
|
+
}
|
|
4483
|
+
try {
|
|
4484
|
+
const newCostCents = calculateCost(record.provider, record.model, record.usage);
|
|
4485
|
+
return newCostCents;
|
|
4486
|
+
} catch (error) {
|
|
4487
|
+
emitWarning(
|
|
4488
|
+
"WS-003",
|
|
4489
|
+
{
|
|
4490
|
+
taskId: "system",
|
|
4491
|
+
subsystem: "telemetry",
|
|
4492
|
+
executionPhase: "execution"
|
|
4493
|
+
},
|
|
4494
|
+
{
|
|
4495
|
+
severity: "WARN" /* WARN */,
|
|
4496
|
+
metadata: {
|
|
4497
|
+
operation: "recalculate_cost",
|
|
4498
|
+
executionId,
|
|
4499
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4502
|
+
);
|
|
4503
|
+
return null;
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
/**
|
|
4507
|
+
* Get all execution costs for a task
|
|
4508
|
+
*/
|
|
4509
|
+
getTaskExecutionCosts(taskId) {
|
|
4510
|
+
const stmt = this.db.prepare(`
|
|
4511
|
+
SELECT ${EXECUTION_COST_COLUMNS}
|
|
4512
|
+
FROM enriched_invocations e
|
|
4513
|
+
JOIN model_invocations i ON e.invocation_id = i.id
|
|
4514
|
+
WHERE e.task_id = ?
|
|
4515
|
+
ORDER BY i.timestamp ASC
|
|
4516
|
+
`);
|
|
4517
|
+
const rows = stmt.all(taskId);
|
|
4518
|
+
const mappedRecords = rows.map((row) => {
|
|
4519
|
+
const usage = row.usage_data ? parseExecutionCostUsage(row.usage_data) : null;
|
|
4520
|
+
const resolvedCostSnapshot = resolveExecutionCostSnapshot(row);
|
|
4521
|
+
if (!usage || !resolvedCostSnapshot) {
|
|
4522
|
+
return null;
|
|
4523
|
+
}
|
|
4524
|
+
return projectExecutionCostRecord(row, usage, resolvedCostSnapshot);
|
|
4525
|
+
});
|
|
4526
|
+
return mappedRecords.filter((record) => record !== null);
|
|
4527
|
+
}
|
|
4528
|
+
/**
|
|
4529
|
+
* Get all execution costs for a competition
|
|
4530
|
+
*/
|
|
4531
|
+
getCompetitionExecutionCosts(competitionId) {
|
|
4532
|
+
const stmt = this.db.prepare(`
|
|
4533
|
+
SELECT ${EXECUTION_COST_COLUMNS}
|
|
4534
|
+
FROM enriched_invocations e
|
|
4535
|
+
JOIN model_invocations i ON e.invocation_id = i.id
|
|
4536
|
+
WHERE e.competition_id = ?
|
|
4537
|
+
ORDER BY i.timestamp ASC
|
|
4538
|
+
`);
|
|
4539
|
+
const rows = stmt.all(competitionId);
|
|
4540
|
+
const mappedRecords = rows.map((row) => {
|
|
4541
|
+
const usage = row.usage_data ? parseExecutionCostUsage(row.usage_data) : null;
|
|
4542
|
+
const resolvedCostSnapshot = resolveExecutionCostSnapshot(row);
|
|
4543
|
+
if (!usage || !resolvedCostSnapshot) {
|
|
4544
|
+
return null;
|
|
4545
|
+
}
|
|
4546
|
+
return projectExecutionCostRecord(row, usage, resolvedCostSnapshot);
|
|
4547
|
+
});
|
|
4548
|
+
return mappedRecords.filter((record) => record !== null);
|
|
4549
|
+
}
|
|
4550
|
+
/**
|
|
4551
|
+
* Count invocations that have not yet been enriched with cost data.
|
|
4552
|
+
*/
|
|
4553
|
+
countUnenrichedInvocations() {
|
|
4554
|
+
const stmt = this.db.prepare(`
|
|
4555
|
+
SELECT COUNT(*) as count
|
|
4556
|
+
FROM model_invocations i
|
|
4557
|
+
LEFT JOIN enriched_invocations e ON i.id = e.invocation_id
|
|
4558
|
+
WHERE e.id IS NULL
|
|
4559
|
+
`);
|
|
4560
|
+
const row = stmt.get();
|
|
4561
|
+
return row?.count ?? 0;
|
|
4562
|
+
}
|
|
4563
|
+
/**
|
|
4564
|
+
* Count enriched invocations since a given timestamp.
|
|
4565
|
+
*/
|
|
4566
|
+
countEnrichedInvocationsSince(startTimestampMs) {
|
|
4567
|
+
const stmt = this.db.prepare(`
|
|
4568
|
+
SELECT COUNT(*) as count
|
|
4569
|
+
FROM enriched_invocations e
|
|
4570
|
+
JOIN model_invocations i ON e.invocation_id = i.id
|
|
4571
|
+
WHERE i.timestamp >= ?
|
|
4572
|
+
`);
|
|
4573
|
+
const row = stmt.get(startTimestampMs);
|
|
4574
|
+
return row?.count ?? 0;
|
|
4575
|
+
}
|
|
4576
|
+
/**
|
|
4577
|
+
* Count enriched invocations in the window whose per-token cost is
|
|
4578
|
+
* unavailable (no usage evidence, no registry snapshot, or no pricing for the
|
|
4579
|
+
* model). Enrichment leaves `computed_cost_cents` NULL with a NULL billing
|
|
4580
|
+
* mode for those rows, so cost readbacks can report how many invocations a
|
|
4581
|
+
* total does not account for instead of implying a complete $0.
|
|
4582
|
+
*
|
|
4583
|
+
* Seat-licensed (non-metered) rows also carry a NULL cost but are excluded:
|
|
4584
|
+
* their absence from per-token totals is by design, not missing evidence.
|
|
4585
|
+
*/
|
|
4586
|
+
countUnpricedInvocationsSince(startTimestampMs) {
|
|
4587
|
+
const stmt = this.db.prepare(`
|
|
4588
|
+
SELECT COUNT(*) as count
|
|
4589
|
+
FROM enriched_invocations e
|
|
4590
|
+
JOIN model_invocations i ON e.invocation_id = i.id
|
|
4591
|
+
WHERE i.timestamp >= ?
|
|
4592
|
+
AND e.computed_cost_cents IS NULL
|
|
4593
|
+
AND (e.billing_mode IS NULL OR e.billing_mode = 'metered')
|
|
4594
|
+
`);
|
|
4595
|
+
const row = stmt.get(startTimestampMs);
|
|
4596
|
+
return row?.count ?? 0;
|
|
4597
|
+
}
|
|
4598
|
+
/**
|
|
4599
|
+
* Whether any invocation in the window reported non-zero token usage in a
|
|
4600
|
+
* dimension the cost formula prices.
|
|
4601
|
+
*
|
|
4602
|
+
* Cache reads and writes are billed usage, and providers report them only in
|
|
4603
|
+
* `usage_evidence` — the raw columns stay 0. Reading the raw columns alone
|
|
4604
|
+
* called a cache-only window "zero usage" and then described its real,
|
|
4605
|
+
* positive cost as $0.
|
|
4606
|
+
*/
|
|
4607
|
+
hasTokenUsageSince(startTimestampMs) {
|
|
4608
|
+
const stmt = this.db.prepare(`
|
|
4609
|
+
SELECT 1
|
|
4610
|
+
FROM model_invocations i
|
|
4611
|
+
LEFT JOIN usage_evidence u ON u.invocation_id = i.id
|
|
4612
|
+
WHERE i.timestamp >= ?
|
|
4613
|
+
AND (
|
|
4614
|
+
COALESCE(i.tokens_in, 0) > 0
|
|
4615
|
+
OR COALESCE(i.tokens_out, 0) > 0
|
|
4616
|
+
OR COALESCE(u.input_tokens, 0) > 0
|
|
4617
|
+
OR COALESCE(u.output_tokens, 0) > 0
|
|
4618
|
+
OR COALESCE(u.cache_read_tokens, 0) > 0
|
|
4619
|
+
OR COALESCE(u.cache_write_tokens, 0) > 0
|
|
4620
|
+
)
|
|
4621
|
+
LIMIT 1
|
|
4622
|
+
`);
|
|
4623
|
+
return Boolean(stmt.get(startTimestampMs));
|
|
4624
|
+
}
|
|
4625
|
+
/**
|
|
4626
|
+
* How many unpriced invocations in the window did record token counts.
|
|
4627
|
+
*
|
|
4628
|
+
* An explicit 0 counts as recorded: such a row is unpriced because pricing
|
|
4629
|
+
* could not be resolved (no snapshot, provider, model, or rates), not because
|
|
4630
|
+
* usage was never reported, and cost output explains the two differently. A
|
|
4631
|
+
* count rather than a flag so a window holding both kinds reports each.
|
|
4632
|
+
*/
|
|
4633
|
+
countUnpricedWithTokenEvidenceSince(startTimestampMs) {
|
|
4634
|
+
const stmt = this.db.prepare(`
|
|
4635
|
+
SELECT COUNT(DISTINCT i.id) as count
|
|
4636
|
+
FROM enriched_invocations e
|
|
4637
|
+
JOIN model_invocations i ON e.invocation_id = i.id
|
|
4638
|
+
LEFT JOIN usage_evidence u ON u.invocation_id = i.id
|
|
4639
|
+
WHERE i.timestamp >= ?
|
|
4640
|
+
AND e.computed_cost_cents IS NULL
|
|
4641
|
+
AND (e.billing_mode IS NULL OR e.billing_mode = 'metered')
|
|
4642
|
+
AND (
|
|
4643
|
+
i.tokens_in IS NOT NULL
|
|
4644
|
+
OR i.tokens_out IS NOT NULL
|
|
4645
|
+
OR u.input_tokens IS NOT NULL
|
|
4646
|
+
OR u.output_tokens IS NOT NULL
|
|
4647
|
+
OR u.cache_read_tokens IS NOT NULL
|
|
4648
|
+
OR u.cache_write_tokens IS NOT NULL
|
|
4649
|
+
)
|
|
4650
|
+
`);
|
|
4651
|
+
const row = stmt.get(startTimestampMs);
|
|
4652
|
+
return row?.count ?? 0;
|
|
4653
|
+
}
|
|
4654
|
+
/**
|
|
4655
|
+
* Count raw model invocations since a given timestamp (before cost enrichment).
|
|
4656
|
+
*/
|
|
4657
|
+
countRawInvocationsSince(startTimestampMs) {
|
|
4658
|
+
const stmt = this.db.prepare(`
|
|
4659
|
+
SELECT COUNT(*) as count
|
|
4660
|
+
FROM model_invocations
|
|
4661
|
+
WHERE timestamp >= ?
|
|
4662
|
+
`);
|
|
4663
|
+
const row = stmt.get(startTimestampMs);
|
|
4664
|
+
return row?.count ?? 0;
|
|
4665
|
+
}
|
|
4666
|
+
/**
|
|
4667
|
+
* Get execution costs since a given timestamp
|
|
4668
|
+
*/
|
|
4669
|
+
getExecutionCostsSince(startTimestampMs) {
|
|
4670
|
+
const stmt = this.db.prepare(`
|
|
4671
|
+
SELECT ${EXECUTION_COST_COLUMNS},
|
|
4672
|
+
i.tokens_in,
|
|
4673
|
+
i.tokens_out
|
|
4674
|
+
FROM enriched_invocations e
|
|
4675
|
+
JOIN model_invocations i ON e.invocation_id = i.id
|
|
4676
|
+
WHERE i.timestamp >= ?
|
|
4677
|
+
ORDER BY i.timestamp ASC
|
|
4678
|
+
`);
|
|
4679
|
+
const rows = stmt.all(startTimestampMs);
|
|
4680
|
+
const mappedRecords = rows.map((row) => {
|
|
4681
|
+
const usage = row.usage_data ? parseExecutionCostUsage(row.usage_data) : {
|
|
4682
|
+
inputTokens: row.tokens_in ?? 0,
|
|
4683
|
+
outputTokens: row.tokens_out ?? 0
|
|
4684
|
+
};
|
|
4685
|
+
const resolvedCostSnapshot = resolveExecutionCostSnapshot(row);
|
|
4686
|
+
if (!resolvedCostSnapshot) {
|
|
4687
|
+
return null;
|
|
4688
|
+
}
|
|
4689
|
+
return projectExecutionCostRecord(row, usage, resolvedCostSnapshot);
|
|
4690
|
+
});
|
|
4691
|
+
return mappedRecords.filter((record) => record !== null);
|
|
4692
|
+
}
|
|
4693
|
+
/**
|
|
4694
|
+
* Get total cost for a task from cost snapshots.
|
|
4695
|
+
*
|
|
4696
|
+
* Returns a status-bearing amount in cents (as stored on enriched rows).
|
|
4697
|
+
* Known zero is valid only when priced rows exist and sum to 0.
|
|
4698
|
+
* Empty match set, all-null evidence, and query failures do not collapse to bare 0.
|
|
4699
|
+
*/
|
|
4700
|
+
getTaskTotalCost(taskId) {
|
|
4701
|
+
try {
|
|
4702
|
+
const stmt = this.db.prepare(`
|
|
4703
|
+
SELECT
|
|
4704
|
+
${SNAPSHOT_FIRST_COST_CENTS_SQL} AS cost_cents
|
|
4705
|
+
FROM enriched_invocations e
|
|
4706
|
+
WHERE e.task_id = ?
|
|
4707
|
+
`);
|
|
4708
|
+
const rows = stmt.all(taskId);
|
|
4709
|
+
if (rows.length === 0) {
|
|
4710
|
+
return unavailableCostAmount(
|
|
4711
|
+
"cents",
|
|
4712
|
+
ENRICHED_INVOCATIONS_SOURCE,
|
|
4713
|
+
`no enriched cost rows for task=${taskId}`
|
|
4714
|
+
);
|
|
4715
|
+
}
|
|
4716
|
+
let totalCents = 0;
|
|
4717
|
+
let pricedCount = 0;
|
|
4718
|
+
for (const row of rows) {
|
|
4719
|
+
if (row.cost_cents === null || row.cost_cents === void 0) {
|
|
4720
|
+
continue;
|
|
4721
|
+
}
|
|
4722
|
+
if (!Number.isFinite(row.cost_cents)) {
|
|
4723
|
+
return unavailableCostAmount(
|
|
4724
|
+
"cents",
|
|
4725
|
+
ENRICHED_INVOCATIONS_SOURCE,
|
|
4726
|
+
`non-finite cost evidence for task=${taskId}`
|
|
4727
|
+
);
|
|
4728
|
+
}
|
|
4729
|
+
pricedCount += 1;
|
|
4730
|
+
totalCents += row.cost_cents;
|
|
4731
|
+
}
|
|
4732
|
+
if (pricedCount === 0) {
|
|
4733
|
+
return unavailableCostAmount(
|
|
4734
|
+
"cents",
|
|
4735
|
+
ENRICHED_INVOCATIONS_SOURCE,
|
|
4736
|
+
`null or missing cost evidence for task=${taskId}`
|
|
4737
|
+
);
|
|
4738
|
+
}
|
|
4739
|
+
if (pricedCount < rows.length) {
|
|
4740
|
+
return unavailableCostAmount(
|
|
4741
|
+
"cents",
|
|
4742
|
+
ENRICHED_INVOCATIONS_SOURCE,
|
|
4743
|
+
`partial cost evidence for task=${taskId} (${pricedCount}/${rows.length} priced)`
|
|
4744
|
+
);
|
|
4745
|
+
}
|
|
4746
|
+
return availableCostAmount(totalCents, "cents", ENRICHED_INVOCATIONS_SOURCE);
|
|
4747
|
+
} catch (error) {
|
|
4748
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4749
|
+
return errorCostAmount(
|
|
4750
|
+
"cents",
|
|
4751
|
+
ENRICHED_INVOCATIONS_SOURCE,
|
|
4752
|
+
message,
|
|
4753
|
+
`query failure for task=${taskId}`
|
|
4754
|
+
);
|
|
4755
|
+
}
|
|
4756
|
+
}
|
|
4757
|
+
/**
|
|
4758
|
+
* Get cost breakdown by provider for a competition.
|
|
4759
|
+
*
|
|
4760
|
+
* Status-bearing (C11): empty match set, null/partial evidence, and query
|
|
4761
|
+
* failures never collapse to an empty map or bare numeric zero. Known zero is
|
|
4762
|
+
* returned only when priced rows exist and sum to 0. Zero-valued providers are
|
|
4763
|
+
* retained rather than omitted.
|
|
4764
|
+
*/
|
|
4765
|
+
getCompetitionCostBreakdown(competitionId) {
|
|
4766
|
+
try {
|
|
4767
|
+
const stmt = this.db.prepare(`
|
|
4768
|
+
SELECT
|
|
4769
|
+
i.provider_id,
|
|
4770
|
+
SUM(${SNAPSHOT_FIRST_COST_CENTS_SQL}) as total_cost_cents,
|
|
4771
|
+
COUNT(*) as row_count,
|
|
4772
|
+
SUM(CASE WHEN ${SNAPSHOT_FIRST_COST_CENTS_SQL} IS NOT NULL THEN 1 ELSE 0 END) as priced_count
|
|
4773
|
+
FROM enriched_invocations e
|
|
4774
|
+
JOIN model_invocations i ON e.invocation_id = i.id
|
|
4775
|
+
WHERE e.competition_id = ?
|
|
4776
|
+
GROUP BY i.provider_id
|
|
4777
|
+
`);
|
|
4778
|
+
const rows = stmt.all(competitionId);
|
|
4779
|
+
if (rows.length === 0) {
|
|
4780
|
+
const total2 = unavailableCostAmount(
|
|
4781
|
+
"cents",
|
|
4782
|
+
ENRICHED_INVOCATIONS_SOURCE,
|
|
4783
|
+
`no enriched cost rows for competition=${competitionId}`
|
|
4784
|
+
);
|
|
4785
|
+
return {
|
|
4786
|
+
status: "unavailable",
|
|
4787
|
+
byProvider: {},
|
|
4788
|
+
total: total2,
|
|
4789
|
+
source: ENRICHED_INVOCATIONS_SOURCE,
|
|
4790
|
+
detail: total2.detail
|
|
4791
|
+
};
|
|
4792
|
+
}
|
|
4793
|
+
const byProvider = {};
|
|
4794
|
+
let allAvailable = true;
|
|
4795
|
+
let totalCents = 0;
|
|
4796
|
+
const partialDetails = [];
|
|
4797
|
+
for (const row of rows) {
|
|
4798
|
+
const rowCount = row.row_count ?? 0;
|
|
4799
|
+
const pricedCount = row.priced_count ?? 0;
|
|
4800
|
+
const totalCostCents = row.total_cost_cents;
|
|
4801
|
+
if (rowCount === 0) {
|
|
4802
|
+
byProvider[row.provider_id] = unavailableCostAmount(
|
|
4803
|
+
"cents",
|
|
4804
|
+
ENRICHED_INVOCATIONS_SOURCE,
|
|
4805
|
+
`no rows for provider=${row.provider_id} competition=${competitionId}`
|
|
4806
|
+
);
|
|
4807
|
+
allAvailable = false;
|
|
4808
|
+
continue;
|
|
4809
|
+
}
|
|
4810
|
+
if (pricedCount === 0 || totalCostCents === null || totalCostCents === void 0) {
|
|
4811
|
+
byProvider[row.provider_id] = unavailableCostAmount(
|
|
4812
|
+
"cents",
|
|
4813
|
+
ENRICHED_INVOCATIONS_SOURCE,
|
|
4814
|
+
`null or missing cost evidence for provider=${row.provider_id} competition=${competitionId}`
|
|
4815
|
+
);
|
|
4816
|
+
allAvailable = false;
|
|
4817
|
+
partialDetails.push(`${row.provider_id}:null`);
|
|
4818
|
+
continue;
|
|
4819
|
+
}
|
|
4820
|
+
if (pricedCount < rowCount) {
|
|
4821
|
+
byProvider[row.provider_id] = unavailableCostAmount(
|
|
4822
|
+
"cents",
|
|
4823
|
+
ENRICHED_INVOCATIONS_SOURCE,
|
|
4824
|
+
`partial cost evidence for provider=${row.provider_id} competition=${competitionId} (${pricedCount}/${rowCount} priced)`
|
|
4825
|
+
);
|
|
4826
|
+
allAvailable = false;
|
|
4827
|
+
partialDetails.push(`${row.provider_id}:${pricedCount}/${rowCount}`);
|
|
4828
|
+
continue;
|
|
4829
|
+
}
|
|
4830
|
+
byProvider[row.provider_id] = availableCostAmount(
|
|
4831
|
+
totalCostCents,
|
|
4832
|
+
"cents",
|
|
4833
|
+
ENRICHED_INVOCATIONS_SOURCE
|
|
4834
|
+
);
|
|
4835
|
+
totalCents += totalCostCents;
|
|
4836
|
+
}
|
|
4837
|
+
if (!allAvailable) {
|
|
4838
|
+
const detail = partialDetails.length > 0 ? `partial or null cost evidence for competition=${competitionId} (${partialDetails.join(", ")})` : `incomplete cost evidence for competition=${competitionId}`;
|
|
4839
|
+
const total2 = unavailableCostAmount("cents", ENRICHED_INVOCATIONS_SOURCE, detail);
|
|
4840
|
+
return {
|
|
4841
|
+
status: "unavailable",
|
|
4842
|
+
byProvider,
|
|
4843
|
+
total: total2,
|
|
4844
|
+
source: ENRICHED_INVOCATIONS_SOURCE,
|
|
4845
|
+
detail
|
|
4846
|
+
};
|
|
4847
|
+
}
|
|
4848
|
+
const total = availableCostAmount(totalCents, "cents", ENRICHED_INVOCATIONS_SOURCE);
|
|
4849
|
+
return {
|
|
4850
|
+
status: "available",
|
|
4851
|
+
byProvider,
|
|
4852
|
+
total,
|
|
4853
|
+
source: ENRICHED_INVOCATIONS_SOURCE
|
|
4854
|
+
};
|
|
4855
|
+
} catch (error) {
|
|
4856
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4857
|
+
const total = errorCostAmount(
|
|
4858
|
+
"cents",
|
|
4859
|
+
ENRICHED_INVOCATIONS_SOURCE,
|
|
4860
|
+
message,
|
|
4861
|
+
`query failure for competition=${competitionId}`
|
|
4862
|
+
);
|
|
4863
|
+
return {
|
|
4864
|
+
status: "error",
|
|
4865
|
+
byProvider: {},
|
|
4866
|
+
total,
|
|
4867
|
+
source: ENRICHED_INVOCATIONS_SOURCE,
|
|
4868
|
+
error: message,
|
|
4869
|
+
detail: total.detail
|
|
4870
|
+
};
|
|
4871
|
+
}
|
|
4872
|
+
}
|
|
4873
|
+
};
|
|
4874
|
+
function generateInvocationId() {
|
|
4875
|
+
return `inv_${Date.now()}_${randomBytes(8).toString("hex")}`;
|
|
4876
|
+
}
|
|
4877
|
+
function resolveInvocationsDbPath(buildDir) {
|
|
4878
|
+
const dbDir = TelemetryPathResolver.getInstance().resolveDbDir(buildDir);
|
|
4879
|
+
const telemetryDir = path5.basename(dbDir) === "db" ? path5.dirname(dbDir) : dbDir;
|
|
4880
|
+
return path5.join(telemetryDir, "invocations.db");
|
|
4881
|
+
}
|
|
4882
|
+
|
|
4883
|
+
export {
|
|
4884
|
+
NON_METERED_COST_SQL,
|
|
4885
|
+
SNAPSHOT_FIRST_COST_CENTS_SQL,
|
|
4886
|
+
pricingSourceFromRow,
|
|
4887
|
+
billingSourceFromRow,
|
|
4888
|
+
pricingConfidenceFromRow,
|
|
4889
|
+
longContextFromRow,
|
|
4890
|
+
rateCardFromPricingRow,
|
|
4891
|
+
speedTiersFromRow,
|
|
4892
|
+
rateCardFromModelPricing,
|
|
4893
|
+
cursorModelCacheDir,
|
|
4894
|
+
discoverCursorModels,
|
|
4895
|
+
getCachedCursorModelIds,
|
|
4896
|
+
buildCanonicalMappings,
|
|
4897
|
+
getCachedCanonicalMappings,
|
|
4898
|
+
CURSOR_FALLBACK_MODELS,
|
|
4899
|
+
PassthroughResolver,
|
|
4900
|
+
resolvePassthroughPricing,
|
|
4901
|
+
CACHE_WRITE_LONG_TOKENS_DIMENSION,
|
|
4902
|
+
boundRequestEvidence,
|
|
4903
|
+
listPriceUsageFromRecord,
|
|
4904
|
+
computeListPrice,
|
|
4905
|
+
modelEvidenceAvailability,
|
|
4906
|
+
invocationProvenanceClauses,
|
|
4907
|
+
invocationAnalyticsQualityClauses,
|
|
4908
|
+
invocationProvenanceWhereSql,
|
|
4909
|
+
isInvocationMetricQualityRow,
|
|
4910
|
+
createInvocationAnalyticsViews,
|
|
4911
|
+
resolveCompatibleInvocationModelId,
|
|
4912
|
+
invocationModelMatchesSameModel,
|
|
4913
|
+
ModelInvocationsDB,
|
|
4914
|
+
generateInvocationId,
|
|
4915
|
+
resolveInvocationsDbPath,
|
|
4916
|
+
observedSpeedAvailability,
|
|
4917
|
+
requestEvidenceAvailability,
|
|
4918
|
+
requestedServiceTierAvailability,
|
|
4919
|
+
prepareInvocationRegistry,
|
|
4920
|
+
captureInvocationRegistryVersion,
|
|
4921
|
+
ensurePendingInvocationCostEnrichment,
|
|
4922
|
+
listInvocationProviderIdsSince,
|
|
4923
|
+
summarizeInvocationCostVisibility,
|
|
4924
|
+
availableCostAmount,
|
|
4925
|
+
unavailableCostAmount,
|
|
4926
|
+
errorCostAmount,
|
|
4927
|
+
isAvailableCostAmount,
|
|
4928
|
+
costAmountOrNull,
|
|
4929
|
+
projectCostVisibility,
|
|
4930
|
+
fromVisibilitySummary,
|
|
4931
|
+
formatCostProjectionNotice
|
|
4932
|
+
};
|
|
4933
|
+
//# sourceMappingURL=chunk-Q64IR2Z2.js.map
|