routstrd 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/refund.js DELETED
@@ -1,33 +0,0 @@
1
- import crypto from "crypto";
2
-
3
- const token =
4
- "cashuBo2FteCJodHRwczovL21pbnQubWluaWJpdHMuY2FzaC9CaXRjb2luYXVjc2F0YXSBomFpSAAQeTfbDMhlYXCFpGFhGIBhc3hAMDJkZmYwZjUzZGYyYmM1NTJmNmVlZjdmMzhmOWMzZTU5MTY4MGY5ODM5M2I0MjA1YWQ0ZDA5YzlmMTUyNmVkZGFjWCED0Ln-y1Sx8ukCNDoKK4zepISUXAbIMnAj1yuyJGj3hVFhZKNhZVggk4ymtXIrLMRJf2bV7BOo5wmGfPeH1GlYRnG-mPKg-5phc1ggXf-FYynNa0xYgjub6aGIr_mFM-OEAwyx5VlXtWw7NXlhclggM9X2fnOOW8M-cL5Yvn_iAcb7uAcWremL2ZBZi1-TEu2kYWEBYXN4QGQxODY3MTQ4YWJlYTRhMmI5ODIzZDYyNmQ5ZWIwMmJiM2FiYmJlMjNiMzg0Y2U5M2U4N2MyMjAxOTU3YWNiZDVhY1ghA0S4fM1z2KiJKBmGk6qHVvGwnC6t78dcxX0A8Loy8_72YWSjYWVYIKVwButD6sTWCuMrdLGKj0SSdkO_Jl_sP6bAmnL65otzYXNYINBF5s_e9_WmXrI7GZ3n1KliaZk5Q9i4xN2N1JBUlVEdYXJYIGCJVxKUoIUDdtXLz7HFQg7nrBRIb3DJXt6eiveL03EqpGFhAmFzeEAxNTU3NmE4YTQ5Y2RhMzhhZjQ4N2YxYjhlYzk4ZWYwM2Q0MmZlZWY4NThjYzI3YWJlYjlmYmNlNzhlZTBlZWUxYWNYIQKLaUbivnGndI-kvs_u8KvUH0Sd3Mn7fP1iF2MfQPHgvWFko2FlWCCCCI_auq8R_eFrumwLYRHD_Nab7qe4_Ok8hTmGjkQ_FWFzWCANCP4IAbAw38HJktVCeZMScqYR-BMq-1VxIYRLbI-mXmFyWCDe3L3omLIxu02vqDYsQkIjIoy5HVwV5nz-bmB4F32VzaRhYRhAYXN4QDY1ODk1ZjViYTJkOTA4MjJmMzhmZjkyNWJlZDk4Y2Q1ZTM5NDgxNGJjZGI2MDA1ODUyMThlM2EzY2Y0MDM4ZWJhY1ghA0orpNWfYQihhMLqFuqYVdiCRnvzUs0R2LNbmxK0uivLYWSjYWVYIEKL2qFpFy00hsymiyfIpaKoRTbKexGHVIrO4MlF3FjzYXNYILzd6WqS2IDZrmPi2fFC3_w-LQc6aH0Z_-gr_PUL4vtpYXJYIATNPgMmsbuhLQYaWG-c9Fk0QmHrmxZap5SChukvmbM6pGFhEGFzeEA0MWE5OGY2NTlkM2E5YzgwNjIyN2I0Njg0ODljMTAzOGQxNzE5ZGI5ZTIxOTYwMWRkYmQ3ZGRkNTk1YmQ1ZjkwYWNYIQPMufMoE6Of8fyD2MEhPd3Y3KeKhWzepqOZXKYMQewQeGFko2FlWCCCdcrtZzqfHlXkF4Cm_23dztbXX92YiT8qmotuAxoRt2FzWCBTwbmVsAmQraHkJqsrobFhD3WZ8Uc8g8VBMKHlQBwHGmFyWCDOIIFn71ydyJGOeBuxrWc87hCWV1w_6KxzCLhLFIznDQ";
5
- const token2 =
6
- "cashuBo2FteCJodHRwczovL21pbnQubWluaWJpdHMuY2FzaC9CaXRjb2luYXVjc2F0YXSBomFpSAAQeTfbDMhlYXCEpGFhGIBhc3hAMWE2YTEzZWFmMWY5Y2FiZDdjYzJhZDZhOTM0MmU4ZTE1MzJhMzljYmFhMjRiYmEzMTVmYzA0ZDM1ZGY5YzVjZmFjWCEDxc6EeUBD1ykxGkr5se8B6M0KIaoanysabCXFM3kTsTlhZKNhZVgggsoE5igqfQrqxxpuA4h7IOlu58rTPd5fgSDbt0PsvJ5hc1ggN1c8DaDHAKP7JeskTBCKMYod8Hbc1UASWMb1PdNYiithclggiFuUJSZ4xQKXpUSF3i56M3wMNfbAOoime-NEAJNvbzukYWEYQGFzeEBkZmNhNDJmNDYyMDcyOTFmNGRjNjY5NzE3MzVmMWY1NmRhNmVhODg3OGZhYTZjMzJkOGZhODEyMWI3YzU5NWE5YWNYIQPvTgwE_lEc4y5AvoR4afrvVeBg8aFaZbX-J2pi1ODQD2Fko2FlWCCKHJQPvkqTBzXSlGi7IohfC7dmZYKnJstkrh090EAAJmFzWCCL6_TLTYOtlPtb7E5zpJrlAwmVoaEeasDbWFdHq8bts2FyWCAkwP2KY06reSkqAre6S0eNp6tH6MOamrBYnMIdkTKCD6RhYRBhc3hAOTYwOTBhOWRhNDM0NzgwM2YyODMyM2MyZTQwNTJmMzE4MjBmMGVmZGMzMTBkMzYwNzZkYzc1NzU1MDRkNzgyZGFjWCECLAnqF1xnlwRHKkgcEpl1w73wXSco_TrqvHzvADRur-NhZKNhZVggs79rTYHAsMXduTS8VRFr17wY8BeS76I2w0plRSzGBdNhc1ggn5YnBEqQhPH7WbiQOIVmVoUL6DuOjXghnH6jsqEiVJphclggl5PftuvVrNaO-FaqHB_iALFFtAlccnG87PIWaTxW7NqkYWECYXN4QDcwNjQ5NWU4YWJkMDIyNjY2ZTc5ZjkwNWU2ZTk5NzU2MmVkZmJkZDU1NDA5NDMwMjNlYjA2ZjU5NDAzNjE2YTlhY1ghA_5PsdXzk5DXHulsXP6n9IadnIYcgqBiKo2ssr7APBFsYWSjYWVYIBj6uJBqZ5tJTqxDgPqhrBCJH3WsU8W73LZITtF_MhM8YXNYIDOLWwQWZ6S8y6Tr3IkuP7rfUSbjw2yP1ut5PNIif6KhYXJYIE-CACyXELfybso_kRN4w1uNKSJO7tc4Ke560ZnbLgFq";
7
- const token3 =
8
- "cashuBo2FteCJodHRwczovL21pbnQubWluaWJpdHMuY2FzaC9CaXRjb2luYXVjc2F0YXSBomFpSAAQeTfbDMhlYXCFpGFhGIBhc3hAOGNhMTRlMjc1ZGM3ZTU2ZjgzMmNiMWQzMzg3MWQ1NTA0ZmY4ZDdjNDExYTlmMWIzZmY4NDliMDVlMWYwYjdkZWFjWCECqkWTlYeBbMjf9Gu9EvXuwMZSwahjlLdtoKYVUjGvMGphZKNhZVggZAlbzaB-f1W9V6DCXx9-UBR3Wrx6q5YPkdPzqdl2NLhhc1gg0iX6AuofKqUpxtYzTS8RK7dDSOhNeuqo4d-OAyEOY7thclggZJpwNkrTQmDQdvPgPuMz1UZoxJB2ntuAB_BJ3B8V_-ykYWEIYXN4QDAwZGUzNjc0OWI5OTMxNzUzODIwMWRiN2FmYmFkZGQyYmVkYWIwYzE4MGQ3YTk4YmQwMTU5ODhhNmU2NzNkN2ZhY1ghArRuuYHPk0zZDarPLFVKXKsWJdrOoVrX2cGOmJHlzj1fYWSjYWVYIA3tdTlmP3AWgUm67_8OGecU__-Bznp_5Z4gxdog4j-BYXNYIC4v6A0OeJ1ftOGaJ5xyDaRoeUcvxZC50k1zmptwca2uYXJYIKwoi2amxdoo6DmakRNC9o1BNBbcVLRovVLTZ_HWrwz6pGFhAmFzeEBlZTJjMDdjNDIyNWY1ZDc5NjUxOGEyN2VkYmVhMjVlNWNiODNlMTYyOTBlYzQ1MTA3MmZhMDMyNDZlZjMzYmZjYWNYIQLnKi-IHFdCqOzKcejPC325v_q7DDBWG4kGbEeEfYj1wmFko2FlWCDJFzWJNhHqQVocBT34rokTvd5MNc2l0yd-22mlrA_J42FzWCCnrH5rsQBu8M5xzz0jdh6iDw798PKXwIjjThsBrJmJ5GFyWCAP5YPIf8NRVQACOX8qTmCFJLly6MZdrPpLKW9crxX3TKRhYRhAYXN4QGU1YTVkODczY2JiN2ZiYjkwZjBhNWJhMjcwODZlNWUwNzczYjhhZmM2MjkxYjkyZDZjNmRjNWUyOWIwNjBmYThhY1ghAsOHo3BVNDkz9aexnLCBGC7fbjAt9qrkMLd5MFSwlkxDYWSjYWVYILVffHeqv2Wj3XBCAvVs5XMizFyO6QXKJX-WkgYejDbhYXNYIHwpn3nGOU8qIU05mvUoLjsQPC50iSp-m0pQc5h_TojGYXJYIFqpEyKa6zmx-es7MGHrBjS34unW7gHirPiWBtNhOYPjpGFhCGFzeEBjZTdjNWQwYTdhMTA1YjFhMGFlODdmNjQ5YjgxY2Y5MDUzNDBkZDg2ZWRmY2EyZDg5ZGI3ZmIwMGU3ZTEwNmFjYWNYIQKHOiRDX17csadc2W9ApXSC9z9OA9Ow_B_0Z3t-LjXlVWFko2FlWCA_BVVJUfd_8aqq6wyc2hna274nMlFJVcPUap8jfLHdZ2FzWCBf7TMpb7gy4H5p2JU4QU9aftsngHLoNRT03e-9IxpdtWFyWCAONKjqsMSZODlyVgbtHJH2Hs3R25mlK3tbM-GsR5KGJQ";
9
- const token4 =
10
- "cashuBo2FteCJodHRwczovL21pbnQubWluaWJpdHMuY2FzaC9CaXRjb2luYXVjc2F0YXSBomFpSAAQeTfbDMhlYXCFpGFhGEBhc3hAM2JiY2M1OTk4MzA3YWQ2MDcyMTUxYzgyMmQ0N2ZmZGMzYTA4MGQ3MDIwZmM2NGM5MGY0NWYyNDA3ZDYwY2Y3Y2FjWCED1kLTzhP_9Z-emLn9p6ypf5WJnu37aXntJiLGczkuJIhhZKNhZVgg0gm2HX3PRE1e73hD53ETXD9K2sa7g74KME6OPHFyQ2xhc1ggD9yp5h77Lw5iVSeEM0pnvx3iOYi2KZ7USzzupHZkUNRhclggp5x96k0_bpDlYzUpsSS_YejP5xdbRKppx2ryUFkJQzakYWEBYXN4QGY5YzFmMjlkM2Q1MzgxODRjYmViNDA5Yjg4ZTNjM2MzNjU5M2I4NDVjMTA1N2RkNGI4MjY3N2M4NzQ1OWRiZjNhY1ghAzUJP4iEl3rzII-lBI6SD5BgsNPPIyb0otqvCjjeRMEKYWSjYWVYIBB_v8-hl5kgLCUuetZyS8bKYR0qDct9FatrHmktjtIPYXNYIDpeyARyUcMX8VI-aitlYYRrDZwnI0i2JK32h6vW7lNmYXJYIIw64U0Fnur7GKglqxmZLhqGwilg7EEvw7CF59ZpPrXepGFhGIBhc3hAOGZmOGYwODA1Zjg2NWFlMDUzNzY3NGZmMmQ0MTk2YjczNWViZWVkMDYxY2Q4NmQ5MmFkOTVkNjIxNGIyNmFkOWFjWCECJSfVy1j8HGKlIW8WPjaL7laDkh8S74bHgpo827L_yw5hZKNhZVggg24YJokE1Xz_g8rv1Lvs_YtoEHpbNNHnIE1pjJrYYo1hc1ggz5P19DDGdG3V_zNOdMoeBe-OlkhBcIiFFQ1h0ayfatphclgg6__UEWXTYs5oI4_VPh16pa6AYuqNf9IQjT_a4WDK8yKkYWEQYXN4QGMxNmMzM2ZjZTI4YmZhNzc5OWUyYmMwNDA3MzVkNmQzZmFlMTUyYjZkZTE0OTZhNjU5NzlmZjBmYzkwOWJjZDBhY1ghApQQGNGslVLEqkkT-lDnRo2aYqyXoT-_McAz4oS4sa-WYWSjYWVYIHFK50vYiJblhpmrkxfN-p0pyylK2dEinSuobMdJt8rRYXNYIBrTahdM6XVoJ02F9G7L9sLxdDX-I15k9rysVA33bF7iYXJYIIkE3phWIy8O1F-_7uiw8MbyVSvzmnipHdMV572pynoPpGFhAWFzeEA1NGNjM2UyODM1MGM4YzIzNjdlZjFkZjg0NGQ3NzRhMjM0NzYxZTA1OTJkNjg2OTRiMGQzOGMyZWU5MTEzNWQ3YWNYIQNmdhuY0oNhnb4QMBKtyP3QXXpUkmBDD9zTetSgK9op0mFko2FlWCCh5r-bIQFTdyFtpOU6YZoJW3cMDf3ruAdNgElSUPm9JmFzWCCiIT_E7W1gJjuATLUJVd7t1qhkPX94SJRd1f2kmvF1uGFyWCAr4psTvW75ntrNFghNvw1VBJ-Qt6elhDCSGpRcLXNN_Q";
11
- const token5 =
12
- "cashuBo2FteCJodHRwczovL21pbnQubWluaWJpdHMuY2FzaC9CaXRjb2luYXVjc2F0YXSBomFpSAAQeTfbDMhlYXCEpGFhGIBhc3hAMzgxMTViZWY0NzA3MjNmZjczZGJhOTk5YWY2M2MwZDlhMmViMzQ3OTA1MWUzMjM3NjQ4YWFjYTVkMWFhN2Y1MGFjWCECw0MPnt_M_lwq5ZkmMvgtJHVa2A8PYTtbM6-C0do3NF5hZKNhZVggONdGnCnTzAOKKL9v1CnruOayiVpWDwGV9zRRHhh6KX9hc1ggSdj6FOCa1-ub6eI8hF42RbdMonb0-CYwRoWm0fSH7_phclggeQXZy7n8e7RzL1rjqwhYDjcqrUxj1v3aPAM_4cEaauqkYWEYQGFzeEA5NTg5YTFkODEyOTg3MzE0ZDFiYTE5ZDdjZmM1MzZlMTlmNGVkNGVlNTg3MDA4NjRmNjg4NjA0MjI2Mjk5YzFlYWNYIQNWML_D2vtlMYtimuEgIkGNYvaFZFWjyWwkZdyrT2ijeGFko2FlWCD1N0B9J5B91SxMhTg1PhWytUjWJPFOWvlm1UpXJ7UhqWFzWCASNE9dGijBP9jHIgyjnHnHI0G8a7qWsPd8Uhc0PwxS4mFyWCA3vukTqXvkF0Yit9w8wkyPWTIYLxHefjSHzaOuyxI6XaRhYRBhc3hANWJhOWM1MTA3ZTc1MWM4MGE2NzBiMjI2MTUxYjMyZDIzNzNiZjU1M2EwM2VlNTk2ZWQ1NTEyOTFiY2UwMWZmMWFjWCEC6f7l5IXXSaP9eRZmdUYs9liU2dVxNTaNPgPlPgnmzs1hZKNhZVggQaxRZNwg_cPymPLpgWLetqAMuUd5kRfMjNID22snM5dhc1gg0CDW54cjN5HF3FIogQAB3TqowQiH5aJ5ZbHIIKbX59Fhclggolu8liKdOYxCrwmU4p7VsPAflDJecMiUePmPLRmUwS6kYWECYXN4QDY5MDRjM2RkZmJlNjRmOGRmY2NkOWI0NWFmNWEyZTMzYzcyMzIzZTU5N2VkMGNiM2NmZTJhMWIxYzRmOGE4YjZhY1ghA5wv4H1I4NBMUy9qQWavrG6AW3pduPm-F7JUxYt0KC9kYWSjYWVYIIthil2Aj1kiIVmRlziK9NjEI_Dk1JKDn0pazj4Z8qVQYXNYIMNyB7-TbSR_9gNyO_YKohy3D79KhcsbAxV6MLi_RAnEYXJYIM4g3a130UC94KjWYufe-DWVGAQsGNTgd2fLT-DZlrS9";
13
-
14
- // Hash the token with SHA256
15
- const hash = crypto.createHash("sha256").update(token).digest("hex");
16
- console.log("SHA256 hash:", hash);
17
-
18
- // Make the POST request
19
- const url = `https://llm.satsandsports.cash/v1/cashu-refund/${hash}`;
20
-
21
- fetch(url, {
22
- method: "POST",
23
- headers: {
24
- "Content-Type": "application/json",
25
- },
26
- })
27
- .then((res) => res.json())
28
- .then((data) => {
29
- console.log("Response:", JSON.stringify(data, null, 2));
30
- })
31
- .catch((err) => {
32
- console.error("Error:", err.message);
33
- });
package/refund_new.js DELETED
@@ -1,20 +0,0 @@
1
- const token =
2
- "cashuBo2FteCJodHRwczovL21pbnQubWluaWJpdHMuY2FzaC9CaXRjb2luYXVjc2F0YXSBomFpSAAQeTfbDMhlYXCGpGFhGIBhc3hAYzNmMDFjNDYxYjMxNmEwM2E2NGMzNjBmZDJhNjk1OThmMzE2MmM4NGMzZjlmZWExYWYxYTNjN2EyNWY3OTdmNGFjWCEC8iVrY11X2Hs_cYpWG7iD8Xt4DPcIZbVO0RAYoemP7udhZKNhZVggD1RQud6FdadMsbgJ_JB6h-qsJX0qLY83AIig3ocSZdNhc1ggOJIqxJpCn7dIqUO4lLLQE4VMs4TDu-bXWr0Kb3W8cYNhclgg_ZB2BEjqNIemFZ3GGGt-ZROgKNntpcyLX6eAJMYM6p2kYWECYXN4QGRlZjdhMjc5ZmNlZjY2ZmY5YzMyZDczODljYmJmNjUyNGIzY2M0OTQzYmM3MWJkNjA2ZDZiNzdhMjIzODIyMzNhY1ghA591WOMW3zhSXMajVUsgcXzoVReBsnxvq_xuy0gmRSI_YWSjYWVYIASUNdNthr7DNLBsCuIMisKt_KAUNXKVbcvHuUXVrxWCYXNYIBB5rsypISo_LcFKnaqEqbdEBi1SpWRgRLZfbFxqAo1IYXJYIMp7sk2t2k9vh1ZmhnILSlfo2245EqEvjgw8BryNY8QapGFhCGFzeEBhMWY2MzgwYjA2ZmE2NDhlMTY1NmQ5OWNmMThiYWNiYTdhZDVlMmNmNTY1MDAxMTc3NWQ3YjJkNTg5ODkwNzI1YWNYIQKSoj8-ipFq_s6Jes9APqaC5sNUz1icoZ0R6B3HCWoQ3GFko2FlWCAoiS9cHLbzHD9RWU6osZDCfo-czzEidp-pea9ejCEeSGFzWCA_YnzFgPvmXJX65r4QUJQICBcThdC7ZvdZlKqba598pmFyWCBHEneehhb0PxiFrwr6DgSZXWmWKft9WJwXlxr05HHmWqRhYRhAYXN4QDQ3NDkxMWZmYTk4ZTAzYTBkYWM5ODdjMjBjZWI5ZjlkMGE1MGNkNDM1ZjkwMDBhZWFhMjRkMGJlODgzMGM4ZjFhY1ghA0V01nVSIT46umSZL1XABGFGT4nAPnSYEOHUK5eT5xa-YWSjYWVYIHLOM7_1xe30zcNFFQEXgijF8joTo6yFLFDovHXWCr-sYXNYIILMBt2l053_K8HRRj5P-OWrVhg3oUF9NRYyzFQixEW3YXJYIIOu8UCudw8LMvm87O9X6A2VOS_AtDbXNGHEe2krTPTmpGFhCGFzeEA1N2I0OGNlNTNjMDI2MDM4ZmFlMTcxZjYzM2M5NzYzY2U3ZTA0M2E5MTk0N2VhZDM0Y2JhZDQwZGNmMTI5NmMwYWNYIQJ8KXhq9MuwGEw3xf4CFVpSuLmlMHohE4DByPpeW9NYqWFko2FlWCCHLILey0LgSBMe79A0f8gc4vzoI2AZ5vv53jWH9GUcFmFzWCBB7PcuhuKazJYtRXKMWtmQbRv66qkgdt3I5qetd7DfNGFyWCDBj4R1dWGOulvmQxsbpVJwAAM1RkOdSlgztd1ZOUxHfKRhYQRhc3hAMDJjZWQ4NzZjNGU1MGRjYmNmNDViN2U2MGZjNGRiYThiNzMzNzRkMDU2MWFiMTc0Nzc2ZDNjMmE5MjliOWU0NWFjWCED4OBpEfpelcMak8IAzXO8xRjon5bj5VC3dMjRF1js-gJhZKNhZVggpN8QqMUwG52q-t7xrwkDE6lS8Cw67-_68ERu0X2QuAphc1ggrYyoFeGIT73AT-3gd9tCK_1NYh84s9f63oBytxKTv2Vhclggvi2atyfhijg7QLbdEkNQkrHcW0ocJElhPrNmND9_so8";
3
-
4
- // Make the POST request
5
- const url = `https://routstr.otrta.me/v1/balance/refund`;
6
-
7
- fetch(url, {
8
- method: "POST",
9
- headers: {
10
- "Content-Type": "application/json",
11
- "X-Cashu": `${token}`,
12
- },
13
- })
14
- .then((res) => res.json())
15
- .then((data) => {
16
- console.log("Response:", JSON.stringify(data, null, 2));
17
- })
18
- .catch((err) => {
19
- console.error("Error:", err.message);
20
- });
@@ -1,71 +0,0 @@
1
- # routstr Proxy Cost Logging Issue
2
-
3
- ## Problem
4
-
5
- The routstr proxy at `localhost:8009` returns usage data that differs slightly from what the pi-ai `openai-completions` provider expects.
6
-
7
- ## What routstr Returns
8
-
9
- **Streaming response (last chunk):**
10
- ```json
11
- {
12
- "usage": {
13
- "prompt_tokens": 9,
14
- "completion_tokens": 9,
15
- "total_tokens": 18,
16
- "cost": 0.000018,
17
- "prompt_tokens_details": {
18
- "cached_tokens": 0,
19
- "cache_write_tokens": 0
20
- },
21
- "completion_tokens_details": {
22
- "reasoning_tokens": 0
23
- }
24
- },
25
- "cost": {
26
- "total_usd": 0.000018
27
- }
28
- }
29
- ```
30
-
31
- ## What pi-ai Expects
32
-
33
- The `openai-completions` provider in pi-ai expects the standard OpenAI format:
34
-
35
- ```json
36
- {
37
- "usage": {
38
- "prompt_tokens": 9,
39
- "completion_tokens": 9,
40
- "prompt_tokens_details": {
41
- "cached_tokens": 0
42
- },
43
- "completion_tokens_details": {
44
- "reasoning_tokens": 0
45
- }
46
- }
47
- }
48
- ```
49
-
50
- ## Current Handling
51
-
52
- The pi-ai provider already handles the standard format correctly via `parseChunkUsage()`:
53
- - `input` ← `prompt_tokens - cached_tokens`
54
- - `output` ← `completion_tokens + reasoning_tokens`
55
- - `cacheRead` ← `prompt_tokens_details.cached_tokens`
56
- - `cacheWrite` ← **NOT CURRENTLY PARSED** (hardcoded to 0)
57
-
58
- The provider then calculates cost using `calculateCost()` based on the model's configured cost per million tokens, ignoring any `cost` field from the response.
59
-
60
- ## Gap
61
-
62
- The `parseChunkUsage()` function in `packages/ai/src/providers/openai-completions.ts` does not currently extract:
63
- 1. `cache_write_tokens` from `prompt_tokens_details` (routstr-specific field)
64
-
65
- Currently `cacheWrite` is hardcoded to 0.
66
-
67
- ## Resolution
68
-
69
- The existing pi-ai `openai-completions` provider should work with routstr as-is since routstr returns the standard OpenAI format fields. The usage should be logged correctly if:
70
- 1. `stream_options: { include_usage: true }` is passed
71
- 2. The model has a `cost` configuration in the registry
package/test_box.ts DELETED
@@ -1,15 +0,0 @@
1
- import { renderBox } from "./src/tui/usage/render.ts";
2
- import { stripAnsi } from "./src/tui/usage/terminal.ts";
3
-
4
- const w = 40;
5
- const testBox = renderBox(["Hello World", "Line 2"], w, "Title");
6
- console.log(testBox);
7
- const lines = testBox.split("\n");
8
- lines.forEach((l, i) => console.log(`Line ${i} length: ${stripAnsi(l).length}`));
9
-
10
- console.log("---");
11
-
12
- const testBoxNoTitle = renderBox(["Hello World", "Line 2"], w);
13
- console.log(testBoxNoTitle);
14
- const lines2 = testBoxNoTitle.split("\n");
15
- lines2.forEach((l, i) => console.log(`Line ${i} length: ${stripAnsi(l).length}`));
package/test_chat.sh DELETED
@@ -1,29 +0,0 @@
1
- #!/bin/bash
2
-
3
- AUTH="Bearer sk-a2d0981bd84ebf214f4bfb861a273873bfe3e10e6af97533"
4
- BASE_URL="http://localhost:8008/v1/chat/completions"
5
-
6
- echo "=== Testing GPT-4 ==="
7
- curl -s "$BASE_URL" \
8
- -H "Content-Type: application/json" \
9
- -H "Authorization: $AUTH" \
10
- -d '{
11
- "model": "gpt-4",
12
- "messages": [
13
- {"role": "user", "content": "Hello, how are you?"}
14
- ],
15
- "max_tokens": 128
16
- }' | python3 -m json.tool
17
-
18
- echo ""
19
- echo "=== Testing GLM-4.7 ==="
20
- curl -s "$BASE_URL" \
21
- -H "Content-Type: application/json" \
22
- -H "Authorization: $AUTH" \
23
- -d '{
24
- "model": "glm-4.7",
25
- "messages": [
26
- {"role": "user", "content": "Hello, how are you?"}
27
- ],
28
- "max_tokens": 128
29
- }' | python3 -m json.tool
package/test_curl.sh DELETED
@@ -1,11 +0,0 @@
1
- #!/bin/bash
2
- curl -X POST "http://localhost:8008/v1/chat/completions" \
3
- -H "Authorization: Bearer YOUR_API_KEY" \
4
- -H "Content-Type: application/json" \
5
- -d '{
6
- "model": "gemma-3n-e4b-it",
7
- "messages": [
8
- {"role":"system","content":"You are Routstr."},
9
- {"role":"user","content":"Ping the node"}
10
- ]
11
- }'
package/test_split_box.ts DELETED
@@ -1,17 +0,0 @@
1
- import { renderBox } from "./src/tui/usage/render.ts";
2
-
3
- const width = 80;
4
- const halfWidth1 = Math.floor(width / 2);
5
- const halfWidth2 = width - halfWidth1;
6
-
7
- const leftBox = ["Total Spent: 12.78k sats", "Total Requests: 1.0k"];
8
- const rightBox = ["Total Tokens: 25.8M", "Avg Tokens/Req: 25.8K"];
9
-
10
- const leftBoxStr = renderBox(leftBox, halfWidth1, "Stats of Sats");
11
- const rightBoxStr = renderBox(rightBox, halfWidth2, "Token Stats");
12
-
13
- const leftLines = leftBoxStr.split("\n");
14
- const rightLines = rightBoxStr.split("\n");
15
-
16
- const combinedContent = leftLines.map((l, i) => l + (rightLines[i] || " ".repeat(halfWidth2))).join("\n");
17
- console.log(combinedContent);
@@ -1,23 +0,0 @@
1
- import { renderBox } from "./src/tui/usage/render.ts";
2
-
3
- const width = 80;
4
- const halfWidth1 = Math.floor(width / 2);
5
- const halfWidth2 = width - halfWidth1;
6
-
7
- const leftBox = ["Total Spent: 12.78k sats", "Total Requests: 1.0k"];
8
- const rightBox = ["Total Tokens: 25.8M", "Avg Tokens/Req: 25.8K"];
9
-
10
- const leftBoxStr = renderBox(leftBox, halfWidth1, "Stats of Sats");
11
- const rightBoxStr = renderBox(rightBox, halfWidth2, "Token Stats");
12
-
13
- const leftLines = leftBoxStr.split("\n");
14
- const rightLines = rightBoxStr.split("\n");
15
-
16
- const maxLines = Math.max(leftLines.length, rightLines.length);
17
- const combinedLines: string[] = [];
18
- for (let i = 0; i < maxLines; i++) {
19
- const l = leftLines[i] || " ".repeat(Math.floor(width / 2));
20
- const r = rightLines[i] || " ".repeat(Math.ceil(width / 2));
21
- combinedLines.push(l + r);
22
- }
23
- console.log(combinedLines.join("\n"));
@@ -1,55 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { spawnSync } from "child_process";
3
- import { mkdtempSync, rmSync } from "fs";
4
- import { join } from "path";
5
- import { tmpdir } from "os";
6
-
7
- // These scenarios mutate process.env.ROUTSTRD_DIR before the config-store
8
- // module is loaded, and other test files import the same module singleton
9
- // first — so each scenario runs in an isolated bun subprocess where the env
10
- // is guaranteed to be in place before module evaluation.
11
-
12
- const SCENARIO = join(import.meta.dir, "config-store.scenario.ts");
13
-
14
- function runScenario(name: string): { code: number; out: string } {
15
- const dir = mkdtempSync(join(tmpdir(), `routstrd-cfg-${name}-`));
16
- const res = spawnSync("bun", [SCENARIO, name], {
17
- env: {
18
- ...process.env,
19
- NODE_ENV: "test",
20
- ROUTSTRD_DIR: join(dir, "daemon"),
21
- },
22
- stdout: "pipe",
23
- stderr: "pipe",
24
- });
25
- const out = `${res.stdout}${res.stderr}`;
26
- rmSync(dir, { recursive: true, force: true });
27
- // bun's spawnSync reports the exit code in `status` (exitCode is undefined).
28
- return { code: res.status ?? res.exitCode ?? -1, out };
29
- }
30
-
31
- describe("daemon config store permissions", () => {
32
- test("fresh install: config dir 0700, config file 0600, atomic write", () => {
33
- const { code, out } = runScenario("fresh-install");
34
- expect(out).toContain("SCENARIO-OK");
35
- expect(code).toBe(0);
36
- });
37
-
38
- test("existing over-permissive install (0755/0644) is repaired", () => {
39
- const { code, out } = runScenario("repair-perms");
40
- expect(out).toContain("SCENARIO-OK");
41
- expect(code).toBe(0);
42
- });
43
-
44
- test("write failure surfaces synchronously with no temp file left", () => {
45
- const { code, out } = runScenario("write-failure");
46
- expect(out).toContain("SCENARIO-OK");
47
- expect(code).toBe(0);
48
- });
49
-
50
- test("corrupt JSON falls back to defaults", () => {
51
- const { code, out } = runScenario("corrupt-json");
52
- expect(out).toContain("SCENARIO-OK");
53
- expect(code).toBe(0);
54
- });
55
- });
@@ -1,113 +0,0 @@
1
- // Scenario runner for config-store.perms.test.ts — executed as a standalone
2
- // bun process with ROUTSTRD_DIR set before module evaluation. Not a test file.
3
- import {
4
- chmodSync,
5
- existsSync,
6
- readdirSync,
7
- statSync,
8
- writeFileSync,
9
- } from "fs";
10
-
11
- const { CONFIG_DIR, CONFIG_FILE } = await import("../../src/utils/config");
12
- const {
13
- ensureDirs,
14
- ensureDirsSync,
15
- loadDaemonConfig,
16
- loadDaemonConfigSync,
17
- saveDaemonConfig,
18
- REQUESTS_DIR,
19
- } = await import("../../src/daemon/config-store");
20
-
21
- const modeOf = (p: string): number => statSync(p).mode & 0o777;
22
-
23
- const baseConfig = {
24
- port: 8008,
25
- host: "127.0.0.1",
26
- provider: null,
27
- cocodPath: null,
28
- };
29
-
30
- function assert(cond: unknown, msg: string): void {
31
- if (!cond) {
32
- console.error(`ASSERT-FAIL: ${msg}`);
33
- process.exit(1);
34
- }
35
- }
36
-
37
- const scenario = process.argv[2];
38
-
39
- switch (scenario) {
40
- case "fresh-install": {
41
- saveDaemonConfig({
42
- ...baseConfig,
43
- nsec: "nsec1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
44
- });
45
- assert(modeOf(CONFIG_DIR) === 0o700, `dir mode ${modeOf(CONFIG_DIR).toString(8)} != 700`);
46
- assert(modeOf(CONFIG_FILE) === 0o600, `file mode ${modeOf(CONFIG_FILE).toString(8)} != 600`);
47
- assert(
48
- readdirSync(CONFIG_DIR).filter((f) => f.endsWith(".tmp")).length === 0,
49
- "temp file left behind",
50
- );
51
- const loaded = await loadDaemonConfig();
52
- assert(
53
- loaded.nsec === "nsec1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
54
- "nsec round-trip failed",
55
- );
56
- assert(loaded.port === 8008, "port round-trip failed");
57
-
58
- await ensureDirs();
59
- assert(modeOf(CONFIG_DIR) === 0o700, "ensureDirs dir mode");
60
- assert(modeOf(REQUESTS_DIR) === 0o700, "ensureDirs requests dir mode");
61
- break;
62
- }
63
-
64
- case "repair-perms": {
65
- // Simulate an install written by the vulnerable version.
66
- ensureDirsSync();
67
- writeFileSync(CONFIG_FILE, JSON.stringify(baseConfig), { mode: 0o644 });
68
- chmodSync(CONFIG_DIR, 0o755);
69
- chmodSync(CONFIG_FILE, 0o644);
70
- assert(modeOf(CONFIG_FILE) === 0o644, "setup: file not 0644");
71
-
72
- // Load repairs the file mode...
73
- loadDaemonConfigSync();
74
- assert(modeOf(CONFIG_FILE) === 0o600, "load did not repair file mode");
75
-
76
- // ...and the next save repairs the directory mode too.
77
- chmodSync(CONFIG_DIR, 0o755);
78
- saveDaemonConfig({ ...baseConfig, port: 9009 });
79
- assert(modeOf(CONFIG_DIR) === 0o700, "save did not repair dir mode");
80
- assert(modeOf(CONFIG_FILE) === 0o600, "save did not keep file mode");
81
- assert(loadDaemonConfigSync().port === 9009, "save did not persist port");
82
- break;
83
- }
84
-
85
- case "write-failure": {
86
- // Turn the config dir into a regular file: mkdir fails and no temp write
87
- // can succeed, so saveDaemonConfig must surface the error synchronously.
88
- writeFileSync(process.env.ROUTSTRD_DIR!, "blocked");
89
- let threw = false;
90
- try {
91
- saveDaemonConfig(baseConfig);
92
- } catch {
93
- threw = true;
94
- }
95
- assert(threw, "saveDaemonConfig did not throw on unwritable target");
96
- break;
97
- }
98
-
99
- case "corrupt-json": {
100
- ensureDirsSync();
101
- writeFileSync(CONFIG_FILE, "{ not json");
102
- const loaded = await loadDaemonConfig();
103
- assert(loaded.port === 8008, "did not fall back to defaults");
104
- assert(existsSync(CONFIG_FILE), "config file vanished");
105
- break;
106
- }
107
-
108
- default:
109
- console.error(`unknown scenario: ${scenario}`);
110
- process.exit(2);
111
- }
112
-
113
- console.log("SCENARIO-OK");
@@ -1,134 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { parse } from "yaml";
3
- import { mergeHermesConfig } from "../../src/integrations/hermes";
4
-
5
- const ROUTSTR = {
6
- baseUrl: "http://localhost:8008/v1",
7
- apiKey: "routstr-key",
8
- defaultModel: "deepseek-v4-flash",
9
- };
10
-
11
- describe("mergeHermesConfig", () => {
12
- it("preserves the user's model and unrelated custom providers", () => {
13
- const existing = `model:
14
- default: user-model
15
- provider: custom
16
- base_url: https://user.example/v1
17
- api_key: user-key
18
-
19
- custom_providers:
20
- - name: User Provider
21
- base_url: https://user.example/v1
22
- api_key: user-key
23
- model: user-model
24
-
25
- other_setting: true
26
- `;
27
-
28
- const merged = parse(mergeHermesConfig(existing, ROUTSTR));
29
-
30
- expect(merged.model).toEqual({
31
- default: "user-model",
32
- provider: "custom",
33
- base_url: "https://user.example/v1",
34
- api_key: "user-key",
35
- });
36
- expect(merged.other_setting).toBe(true);
37
- expect(merged.custom_providers).toContainEqual({
38
- name: "User Provider",
39
- base_url: "https://user.example/v1",
40
- api_key: "user-key",
41
- model: "user-model",
42
- });
43
- expect(merged.custom_providers).toContainEqual({
44
- name: "Routstr (localhost:8008)",
45
- base_url: ROUTSTR.baseUrl,
46
- api_key: ROUTSTR.apiKey,
47
- model: ROUTSTR.defaultModel,
48
- });
49
- });
50
-
51
- it("does not add a model configuration to an existing Hermes file", () => {
52
- const merged = parse(mergeHermesConfig("other_setting: true\n", ROUTSTR));
53
-
54
- expect(merged.model).toBeUndefined();
55
- expect(merged.other_setting).toBe(true);
56
- });
57
-
58
- it("creates a Routstr default when creating a new Hermes file", () => {
59
- const merged = parse(mergeHermesConfig("", ROUTSTR));
60
-
61
- expect(merged.model).toEqual({
62
- default: ROUTSTR.defaultModel,
63
- provider: "custom",
64
- base_url: ROUTSTR.baseUrl,
65
- api_key: ROUTSTR.apiKey,
66
- });
67
- });
68
-
69
- it("updates an existing Routstr provider in place without duplicating it", () => {
70
- const existing = `custom_providers:
71
- - name: Routstr (old-host:8008)
72
- base_url: http://old-host:8008/v1
73
- api_key: old-key
74
- model: old-model
75
- `;
76
-
77
- const once = mergeHermesConfig(existing, ROUTSTR);
78
- const twice = mergeHermesConfig(once, ROUTSTR);
79
- const merged = parse(twice);
80
- const routstrProviders = merged.custom_providers.filter(
81
- (provider: { name?: string }) => provider.name?.startsWith("Routstr ("),
82
- );
83
-
84
- expect(routstrProviders).toHaveLength(1);
85
- expect(routstrProviders[0]).toEqual({
86
- name: "Routstr (localhost:8008)",
87
- base_url: ROUTSTR.baseUrl,
88
- api_key: ROUTSTR.apiKey,
89
- model: ROUTSTR.defaultModel,
90
- });
91
- expect(twice).toBe(once);
92
- });
93
-
94
- it("repoints model.provider when the Routstr provider is renamed", () => {
95
- const existing = `model:
96
- default: old-model
97
- provider: custom:routstr-(old-host:8008)
98
-
99
- custom_providers:
100
- - name: Routstr (old-host:8008)
101
- base_url: http://old-host:8008/v1
102
- api_key: old-key
103
- model: old-model
104
- `;
105
-
106
- const merged = parse(mergeHermesConfig(existing, ROUTSTR));
107
-
108
- expect(merged.model).toEqual({
109
- default: "old-model",
110
- provider: "custom:routstr-(localhost:8008)",
111
- });
112
- });
113
-
114
- it("leaves model.provider alone when it does not reference the Routstr provider", () => {
115
- const existing = `model:
116
- default: user-model
117
- provider: custom:user-provider
118
-
119
- custom_providers:
120
- - name: Routstr (old-host:8008)
121
- base_url: http://old-host:8008/v1
122
- api_key: old-key
123
- model: old-model
124
- `;
125
-
126
- const merged = parse(mergeHermesConfig(existing, ROUTSTR));
127
-
128
- expect(merged.model.provider).toBe("custom:user-provider");
129
- });
130
-
131
- it("rejects malformed YAML instead of replacing it", () => {
132
- expect(() => mergeHermesConfig("model: [unterminated", ROUTSTR)).toThrow();
133
- });
134
- });
@@ -1,61 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import {
3
- getDaemonBaseUrl,
4
- urlHost,
5
- urlHosts,
6
- } from "../../src/utils/daemon-client";
7
- import type { RoutstrdConfig } from "../../src/utils/config";
8
-
9
- function config(host: string): RoutstrdConfig {
10
- return {
11
- port: 8008,
12
- host,
13
- provider: null,
14
- cocodPath: null,
15
- };
16
- }
17
-
18
- describe("daemon URL host formatting", () => {
19
- test("prefers IPv4 loopback for an IPv4 wildcard", () => {
20
- expect(urlHosts("0.0.0.0")).toEqual(["127.0.0.1", "[::1]"]);
21
- expect(getDaemonBaseUrl(config("0.0.0.0"))).toBe("http://127.0.0.1:8008");
22
- });
23
-
24
- test("prefers IPv6 loopback for an IPv6 wildcard", () => {
25
- expect(urlHosts("::")).toEqual(["[::1]", "127.0.0.1"]);
26
- expect(getDaemonBaseUrl(config("::"))).toBe("http://[::1]:8008");
27
- });
28
-
29
- test("brackets IPv6 literals", () => {
30
- expect(urlHost("2001:db8::1")).toBe("[2001:db8::1]");
31
- expect(getDaemonBaseUrl(config("2001:db8::1"))).toBe(
32
- "http://[2001:db8::1]:8008",
33
- );
34
- });
35
-
36
- test("does not bracket an already-bracketed IPv6 literal twice", () => {
37
- expect(urlHost("[::1]")).toBe("[::1]");
38
- });
39
-
40
- test("escapes an IPv6 zone identifier for use in a URL", () => {
41
- expect(urlHost("fe80::1%en0")).toBe("[fe80::1%25en0]");
42
- });
43
-
44
- test("leaves IPv4 addresses and hostnames unchanged", () => {
45
- expect(urlHost("127.0.0.1")).toBe("127.0.0.1");
46
- expect(urlHost("localhost")).toBe("localhost");
47
- });
48
-
49
- test("uses the default loopback when the host is absent", () => {
50
- expect(urlHost()).toBe("127.0.0.1");
51
- });
52
-
53
- test("preserves an explicitly configured daemon URL", () => {
54
- expect(
55
- getDaemonBaseUrl({
56
- ...config("::"),
57
- daemonUrl: "https://daemon.example/",
58
- }),
59
- ).toBe("https://daemon.example");
60
- });
61
- });