upfynai-code 2.6.0 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -88
- package/bin/cli.js +63 -0
- package/package.json +48 -106
- package/src/auth.js +115 -0
- package/src/config.js +33 -0
- package/src/connect.js +314 -0
- package/src/launch.js +54 -0
- package/src/mcp.js +57 -0
- package/src/server.js +54 -0
- package/client/dist/api-docs.html +0 -879
- package/client/dist/assets/AppContent-C0CyP3g5.js +0 -513
- package/client/dist/assets/CanvasPanel-0u9QR7U-.js +0 -34
- package/client/dist/assets/CanvasPanel-WhZulBJw.css +0 -1
- package/client/dist/assets/DashboardPanel-Dgqw1yZk.js +0 -1
- package/client/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/client/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/client/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/client/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/client/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/client/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/client/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/client/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/client/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/client/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/client/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/client/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/client/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/client/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/client/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/client/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/client/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/client/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/client/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/client/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/client/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/client/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/client/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/client/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/client/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/client/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/client/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/client/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/client/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/client/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/client/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/client/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/client/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/client/dist/assets/LoginModal-CZDEzqjK.js +0 -19
- package/client/dist/assets/MarkdownPreview-CYdvwJaV.js +0 -1
- package/client/dist/assets/Onboarding-DR6NZ4Vz.js +0 -1
- package/client/dist/assets/SetupForm-D49gtWY4.js +0 -1
- package/client/dist/assets/Tableau10-B-NsZVaP.js +0 -1
- package/client/dist/assets/WorkflowsPanel-CqlbEJA_.js +0 -1
- package/client/dist/assets/_commonjs-dynamic-modules-TDtrdbi3.js +0 -1
- package/client/dist/assets/ar-SA-G6X2FPQ2-BWqa1yBH.js +0 -10
- package/client/dist/assets/arc-BegSKqEW.js +0 -1
- package/client/dist/assets/array-BKyUJesY.js +0 -1
- package/client/dist/assets/az-AZ-76LH7QW2-DrVlbZDP.js +0 -1
- package/client/dist/assets/bg-BG-XCXSNQG7-DdunjBgT.js +0 -5
- package/client/dist/assets/blockDiagram-38ab4fdb-BKMbwGHu.js +0 -118
- package/client/dist/assets/bn-BD-2XOGV67Q-_7DtmvwO.js +0 -5
- package/client/dist/assets/c4Diagram-3d4e48cf-hJuiHhSn.js +0 -10
- package/client/dist/assets/ca-ES-6MX7JW3Y-BFIrmojG.js +0 -8
- package/client/dist/assets/channel-Bur-rRTp.js +0 -1
- package/client/dist/assets/classDiagram-70f12bd4-BjiAf9cM.js +0 -2
- package/client/dist/assets/classDiagram-v2-f2320105-pwBewejc.js +0 -2
- package/client/dist/assets/clone-BtqXeoBJ.js +0 -1
- package/client/dist/assets/createText-2e5e7dd3-Dq_acOWe.js +0 -5
- package/client/dist/assets/cs-CZ-2BRQDIVT-B-x4F6TJ.js +0 -11
- package/client/dist/assets/da-DK-5WZEPLOC-Btlc8Dgn.js +0 -5
- package/client/dist/assets/de-DE-XR44H4JA-BVu3ZIoD.js +0 -8
- package/client/dist/assets/directory-open-01563666-DWU9wJ6I.js +0 -1
- package/client/dist/assets/directory-open-4ed118d0-CunoC1EB.js +0 -1
- package/client/dist/assets/edges-e0da2a9e-DH0wVTXR.js +0 -4
- package/client/dist/assets/el-GR-BZB4AONW-h2ll8_ZC.js +0 -10
- package/client/dist/assets/erDiagram-9861fffd-BYezLIR7.js +0 -51
- package/client/dist/assets/es-ES-U4NZUMDT-Cveiulwt.js +0 -9
- package/client/dist/assets/eu-ES-A7QVB2H4-DQluL2PY.js +0 -11
- package/client/dist/assets/fa-IR-HGAKTJCU-BJtcMBSv.js +0 -8
- package/client/dist/assets/fi-FI-Z5N7JZ37-D8NfbVXV.js +0 -6
- package/client/dist/assets/file-open-002ab408-DIuFHtCF.js +0 -1
- package/client/dist/assets/file-open-7c801643-684qeFg4.js +0 -1
- package/client/dist/assets/file-save-3189631c-C1wFhQhH.js +0 -1
- package/client/dist/assets/file-save-745eba88-Bb9F9Kg7.js +0 -1
- package/client/dist/assets/flowDb-956e92f1-scnUykhM.js +0 -10
- package/client/dist/assets/flowDiagram-66a62f08-jVyWsfyU.js +0 -4
- package/client/dist/assets/flowDiagram-v2-96b9c2cf-N6xgi25h.js +0 -1
- package/client/dist/assets/flowchart-elk-definition-4a651766-gKGX3HqR.js +0 -139
- package/client/dist/assets/fr-FR-RHASNOE6-vdj42kC6.js +0 -9
- package/client/dist/assets/ganttDiagram-c361ad54-C2CiWFUP.js +0 -257
- package/client/dist/assets/gitGraphDiagram-72cf32ee-C59Yz2LK.js +0 -70
- package/client/dist/assets/gl-ES-HMX3MZ6V-DQo0TzoP.js +0 -10
- package/client/dist/assets/graph-Dx_H43Kv.js +0 -1
- package/client/dist/assets/he-IL-6SHJWFNN-DKXK5e33.js +0 -10
- package/client/dist/assets/hi-IN-IWLTKZ5I-C2Qgqc0R.js +0 -4
- package/client/dist/assets/hu-HU-A5ZG7DT2-Ss-6vX0m.js +0 -7
- package/client/dist/assets/id-ID-SAP4L64H-D7Wsg1S2.js +0 -10
- package/client/dist/assets/image-blob-reduce.esm-D6s-rqMO.js +0 -7
- package/client/dist/assets/index-3862675e-u8Nv7hHC.js +0 -1
- package/client/dist/assets/index-BVowJdZF.js +0 -97
- package/client/dist/assets/index-ce18TYkg.js +0 -27
- package/client/dist/assets/index-kQoJx-bc.css +0 -1
- package/client/dist/assets/infoDiagram-f8f76790-LmoJYsxo.js +0 -7
- package/client/dist/assets/init-Gi6I4Gst.js +0 -1
- package/client/dist/assets/it-IT-JPQ66NNP-CAPTVl7M.js +0 -11
- package/client/dist/assets/ja-JP-DBVTYXUO-eNVPawR2.js +0 -8
- package/client/dist/assets/journeyDiagram-49397b02-BaJqehpR.js +0 -139
- package/client/dist/assets/kaa-6HZHGXH3-tpuNkKhS.js +0 -1
- package/client/dist/assets/kab-KAB-ZGHBKWFO-Dp83kx4x.js +0 -8
- package/client/dist/assets/kk-KZ-P5N5QNE5-B9IlC6YN.js +0 -1
- package/client/dist/assets/km-KH-HSX4SM5Z-B_KMYaMj.js +0 -11
- package/client/dist/assets/ko-KR-MTYHY66A-yebnUNdb.js +0 -9
- package/client/dist/assets/ku-TR-6OUDTVRD-BR6fh6-5.js +0 -9
- package/client/dist/assets/layout-DLl5Jwcl.js +0 -1
- package/client/dist/assets/line-FpB7omSK.js +0 -1
- package/client/dist/assets/linear-CkXqUFJ8.js +0 -1
- package/client/dist/assets/lt-LT-XHIRWOB4-SutZSWtR.js +0 -3
- package/client/dist/assets/lv-LV-5QDEKY6T-DuAxdcZL.js +0 -7
- package/client/dist/assets/mindmap-definition-fc14e90a-DyxXOExh.js +0 -425
- package/client/dist/assets/mr-IN-CRQNXWMA-DqDUWM_8.js +0 -13
- package/client/dist/assets/my-MM-5M5IBNSE-C40kMFMR.js +0 -1
- package/client/dist/assets/nb-NO-T6EIAALU-DVij32Ju.js +0 -10
- package/client/dist/assets/nl-NL-IS3SIHDZ-rT84mDYq.js +0 -8
- package/client/dist/assets/nn-NO-6E72VCQL-BBZXBW8V.js +0 -8
- package/client/dist/assets/oc-FR-POXYY2M6-DzjOugOf.js +0 -8
- package/client/dist/assets/ordinal-Cboi1Yqb.js +0 -1
- package/client/dist/assets/pa-IN-N4M65BXN-DD1iU8_F.js +0 -4
- package/client/dist/assets/path-CbwjOpE9.js +0 -1
- package/client/dist/assets/pdf-CE_K4jFx.js +0 -12
- package/client/dist/assets/pdf.worker-BA9kU3Pw.mjs +0 -61080
- package/client/dist/assets/percentages-BXMCSKIN-WVlHS4wx.js +0 -207
- package/client/dist/assets/pica-CQIY57Tf.js +0 -7
- package/client/dist/assets/pieDiagram-8a3498a8-Dd_85qBH.js +0 -35
- package/client/dist/assets/pl-PL-T2D74RX3-ukVXa48G.js +0 -9
- package/client/dist/assets/pt-BR-5N22H2LF-BibawarT.js +0 -9
- package/client/dist/assets/pt-PT-UZXXM6DQ-So3i9l9w.js +0 -9
- package/client/dist/assets/quadrantDiagram-120e2f19-C4dFVDEx.js +0 -7
- package/client/dist/assets/requirementDiagram-deff3bca-DrTO7yFl.js +0 -52
- package/client/dist/assets/ro-RO-JPDTUUEW-DY0Xq_Hd.js +0 -11
- package/client/dist/assets/roundRect-0PYZxl1G.js +0 -1
- package/client/dist/assets/ru-RU-B4JR7IUQ-B7u_Zvkd.js +0 -9
- package/client/dist/assets/sankeyDiagram-04a897e0-D24gfzuS.js +0 -8
- package/client/dist/assets/sequenceDiagram-704730f1-Dgji2XLQ.js +0 -122
- package/client/dist/assets/si-LK-N5RQ5JYF-OejsLzQ_.js +0 -1
- package/client/dist/assets/sk-SK-C5VTKIMK-_vy2Bt-M.js +0 -6
- package/client/dist/assets/sl-SI-NN7IZMDC-DKOl_u2M.js +0 -6
- package/client/dist/assets/stateDiagram-587899a1-CJ8eBaiU.js +0 -1
- package/client/dist/assets/stateDiagram-v2-d93cdb3a-C5K3l-Nt.js +0 -1
- package/client/dist/assets/styles-6aaf32cf-DAKE0jbx.js +0 -207
- package/client/dist/assets/styles-9a916d00-LFAJCgEy.js +0 -160
- package/client/dist/assets/styles-c10674c1-CllKO8NG.js +0 -116
- package/client/dist/assets/subset-shared.chunk-Uy-J87FQ.js +0 -84
- package/client/dist/assets/subset-worker.chunk-dvgDvqt9.js +0 -1
- package/client/dist/assets/sv-SE-XGPEYMSR-CDCB2ZV5.js +0 -10
- package/client/dist/assets/svgDrawCommon-08f97a94-CObOzbFQ.js +0 -1
- package/client/dist/assets/ta-IN-2NMHFXQM-DHUNdO69.js +0 -9
- package/client/dist/assets/th-TH-HPSO5L25-zI2hnBq3.js +0 -2
- package/client/dist/assets/timeline-definition-85554ec2-C2XHRmxK.js +0 -61
- package/client/dist/assets/tr-TR-DEFEU3FU-l-6Hu4-D.js +0 -7
- package/client/dist/assets/uk-UA-QMV73CPH-CqSOwrl7.js +0 -6
- package/client/dist/assets/vendor-codemirror-D_s0aGBu.js +0 -35
- package/client/dist/assets/vendor-i18n-DCFGyhQR.js +0 -1
- package/client/dist/assets/vendor-icons-Lb69KSFJ.js +0 -646
- package/client/dist/assets/vendor-markdown-BXEi_H3G.js +0 -298
- package/client/dist/assets/vendor-react-9mUTKBHH.js +0 -67
- package/client/dist/assets/vendor-syntax-DnmwQQJF.js +0 -16
- package/client/dist/assets/vendor-xterm-CZq1hqo1.js +0 -66
- package/client/dist/assets/vendor-xterm-qxJ8_QYu.css +0 -32
- package/client/dist/assets/vi-VN-M7AON7JQ-CUL8-mBZ.js +0 -5
- package/client/dist/assets/xychartDiagram-e933f94c-1fmf6slj.js +0 -7
- package/client/dist/assets/zh-CN-LNUGB5OW-CB5y5VVU.js +0 -10
- package/client/dist/assets/zh-HK-E62DVLB3-BHcrrEeJ.js +0 -1
- package/client/dist/assets/zh-TW-RAJ6MFWO-DoDUdkaJ.js +0 -9
- package/client/dist/clear-cache.html +0 -85
- package/client/dist/convert-icons.md +0 -53
- package/client/dist/favicon.png +0 -0
- package/client/dist/favicon.svg +0 -9
- package/client/dist/generate-icons.js +0 -49
- package/client/dist/icons/claude-ai-icon.svg +0 -1
- package/client/dist/icons/codex-white.svg +0 -3
- package/client/dist/icons/codex.svg +0 -3
- package/client/dist/icons/cursor-white.svg +0 -12
- package/client/dist/icons/cursor.svg +0 -1
- package/client/dist/icons/icon-128x128.png +0 -0
- package/client/dist/icons/icon-128x128.svg +0 -12
- package/client/dist/icons/icon-144x144.png +0 -0
- package/client/dist/icons/icon-144x144.svg +0 -12
- package/client/dist/icons/icon-152x152.png +0 -0
- package/client/dist/icons/icon-152x152.svg +0 -12
- package/client/dist/icons/icon-192x192.png +0 -0
- package/client/dist/icons/icon-192x192.svg +0 -12
- package/client/dist/icons/icon-384x384.png +0 -0
- package/client/dist/icons/icon-384x384.svg +0 -12
- package/client/dist/icons/icon-512x512.png +0 -0
- package/client/dist/icons/icon-512x512.svg +0 -12
- package/client/dist/icons/icon-72x72.png +0 -0
- package/client/dist/icons/icon-72x72.svg +0 -12
- package/client/dist/icons/icon-96x96.png +0 -0
- package/client/dist/icons/icon-96x96.svg +0 -12
- package/client/dist/icons/icon-template.svg +0 -12
- package/client/dist/index.html +0 -128
- package/client/dist/logo-128.png +0 -0
- package/client/dist/logo-256.png +0 -0
- package/client/dist/logo-32.png +0 -0
- package/client/dist/logo-512.png +0 -0
- package/client/dist/logo-64.png +0 -0
- package/client/dist/logo.svg +0 -17
- package/client/dist/manifest.json +0 -61
- package/client/dist/mcp-docs.html +0 -119
- package/client/dist/screenshots/cli-selection.png +0 -0
- package/client/dist/screenshots/desktop-main.png +0 -0
- package/client/dist/screenshots/mobile-chat.png +0 -0
- package/client/dist/screenshots/tools-modal.png +0 -0
- package/client/dist/sw.js +0 -19
- package/commands/upfynai-connect.md +0 -59
- package/commands/upfynai-disconnect.md +0 -31
- package/commands/upfynai-doctor.md +0 -99
- package/commands/upfynai-export.md +0 -49
- package/commands/upfynai-local.md +0 -82
- package/commands/upfynai-status.md +0 -75
- package/commands/upfynai-stop.md +0 -49
- package/commands/upfynai-uninstall.md +0 -58
- package/commands/upfynai.md +0 -69
- package/scripts/build-client.js +0 -17
- package/scripts/fix-node-pty.js +0 -67
- package/scripts/install-commands.js +0 -78
- package/server/claude-sdk.js +0 -714
- package/server/cli-ui.js +0 -785
- package/server/cli.js +0 -596
- package/server/constants/config.js +0 -31
- package/server/cursor-cli.js +0 -270
- package/server/database/auth.db +0 -0
- package/server/database/db.js +0 -822
- package/server/database/init.sql +0 -70
- package/server/index.js +0 -2738
- package/server/load-env.js +0 -26
- package/server/mcp-server.js +0 -621
- package/server/middleware/auth.js +0 -181
- package/server/openai-codex.js +0 -403
- package/server/openrouter.js +0 -137
- package/server/projects.js +0 -1742
- package/server/relay-client.js +0 -672
- package/server/routes/agent.js +0 -1226
- package/server/routes/auth.js +0 -266
- package/server/routes/cli-auth.js +0 -263
- package/server/routes/codex.js +0 -344
- package/server/routes/commands.js +0 -598
- package/server/routes/cursor.js +0 -807
- package/server/routes/dashboard.js +0 -205
- package/server/routes/git.js +0 -1151
- package/server/routes/mcp-utils.js +0 -48
- package/server/routes/mcp.js +0 -535
- package/server/routes/payments.js +0 -172
- package/server/routes/projects.js +0 -552
- package/server/routes/settings.js +0 -261
- package/server/routes/taskmaster.js +0 -1928
- package/server/routes/user.js +0 -106
- package/server/routes/vapi-chat.js +0 -94
- package/server/routes/voice.js +0 -194
- package/server/routes/webhooks.js +0 -166
- package/server/routes/workflows.js +0 -118
- package/server/sandbox.js +0 -120
- package/server/services/whisperService.js +0 -84
- package/server/services/workflowScheduler.js +0 -186
- package/server/utils/commandParser.js +0 -303
- package/server/utils/gitConfig.js +0 -24
- package/server/utils/mcp-detector.js +0 -198
- package/server/utils/taskmaster-websocket.js +0 -129
- package/shared/modelConstants.js +0 -96
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import jwt from 'jsonwebtoken';
|
|
2
|
-
import crypto from 'crypto';
|
|
3
|
-
import { userDb, relayTokensDb } from '../database/db.js';
|
|
4
|
-
import { IS_PLATFORM } from '../constants/config.js';
|
|
5
|
-
|
|
6
|
-
let JWT_SECRET = process.env.JWT_SECRET?.trim();
|
|
7
|
-
if (!JWT_SECRET) {
|
|
8
|
-
if (IS_PLATFORM) {
|
|
9
|
-
// In local/self-hosted mode, generate a random secret (auth is bypassed anyway)
|
|
10
|
-
JWT_SECRET = crypto.randomBytes(32).toString('hex');
|
|
11
|
-
} else {
|
|
12
|
-
console.error('[SECURITY] JWT_SECRET environment variable is required. Server cannot start without it.');
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Optional static API key middleware
|
|
18
|
-
const validateApiKey = (req, res, next) => {
|
|
19
|
-
if (!process.env.API_KEY) return next();
|
|
20
|
-
const apiKey = req.headers['x-api-key'];
|
|
21
|
-
if (apiKey !== process.env.API_KEY.trim()) {
|
|
22
|
-
return res.status(401).json({ error: 'Invalid API key' });
|
|
23
|
-
}
|
|
24
|
-
next();
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// Extract JWT from request: cookie → Bearer header → query param (SSE only)
|
|
28
|
-
const extractToken = (req) => {
|
|
29
|
-
// 1. httpOnly cookie (browser sessions — primary auth method)
|
|
30
|
-
if (req.cookies?.session) return req.cookies.session;
|
|
31
|
-
|
|
32
|
-
// 2. Bearer header (API clients, MCP)
|
|
33
|
-
const authHeader = req.headers['authorization'];
|
|
34
|
-
if (authHeader?.startsWith('Bearer ')) return authHeader.slice(7);
|
|
35
|
-
|
|
36
|
-
// 3. Query param — for GET requests (SSE EventSource + iframe embedding)
|
|
37
|
-
if (req.query?.token && req.method === 'GET') {
|
|
38
|
-
return req.query.token;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return null;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
// JWT authentication middleware
|
|
45
|
-
const authenticateToken = async (req, res, next) => {
|
|
46
|
-
// Platform mode: use first database user
|
|
47
|
-
if (IS_PLATFORM) {
|
|
48
|
-
try {
|
|
49
|
-
const user = await userDb.getFirstUser();
|
|
50
|
-
if (!user) return res.status(500).json({ error: 'Platform mode: No user found in database' });
|
|
51
|
-
req.user = user;
|
|
52
|
-
return next();
|
|
53
|
-
} catch (error) {
|
|
54
|
-
console.error('Platform mode error:', error);
|
|
55
|
-
return res.status(500).json({ error: 'Platform mode: Failed to fetch user' });
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const token = extractToken(req);
|
|
60
|
-
if (!token) {
|
|
61
|
-
return res.status(401).json({ error: 'Access denied. No token provided.' });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const decoded = jwt.verify(token, JWT_SECRET);
|
|
66
|
-
const user = await userDb.getUserById(decoded.userId);
|
|
67
|
-
if (!user) return res.status(401).json({ error: 'Invalid token. User not found.' });
|
|
68
|
-
req.user = user;
|
|
69
|
-
// If token came from query param, set session cookie for subsequent requests (iframe auto-auth)
|
|
70
|
-
if (req.query?.token && !req.cookies?.session) {
|
|
71
|
-
res.cookie('session', token, COOKIE_OPTIONS);
|
|
72
|
-
}
|
|
73
|
-
next();
|
|
74
|
-
} catch (error) {
|
|
75
|
-
return res.status(403).json({ error: 'Invalid or expired token' });
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// Generate JWT token (30-day expiration)
|
|
80
|
-
const generateToken = (user) => {
|
|
81
|
-
return jwt.sign(
|
|
82
|
-
{ userId: user.id, username: user.username },
|
|
83
|
-
JWT_SECRET,
|
|
84
|
-
{ expiresIn: '30d' }
|
|
85
|
-
);
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// Cookie config for httpOnly session
|
|
89
|
-
// Works for both self-hosted (same origin) and split deploy (Vercel proxy → Railway)
|
|
90
|
-
const isSecureEnv = process.env.NODE_ENV === 'production' || !!process.env.VERCEL || !!process.env.RAILWAY_ENVIRONMENT;
|
|
91
|
-
const COOKIE_OPTIONS = {
|
|
92
|
-
httpOnly: true,
|
|
93
|
-
secure: isSecureEnv,
|
|
94
|
-
// 'none' required for cross-origin iframe embedding (Vercel frontend → Railway backend)
|
|
95
|
-
// 'strict' used in local/dev mode where everything is same-origin
|
|
96
|
-
sameSite: isSecureEnv ? 'none' : 'strict',
|
|
97
|
-
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
98
|
-
path: '/',
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
// Set session cookie on response
|
|
102
|
-
const setSessionCookie = (res, token) => {
|
|
103
|
-
res.cookie('session', token, COOKIE_OPTIONS);
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
// Clear session cookie
|
|
107
|
-
const clearSessionCookie = (res) => {
|
|
108
|
-
res.clearCookie('session', { path: '/' });
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
// WebSocket authentication (parse cookie from upgrade request headers)
|
|
112
|
-
const authenticateWebSocket = async (request) => {
|
|
113
|
-
// Platform mode: bypass
|
|
114
|
-
if (IS_PLATFORM) {
|
|
115
|
-
try {
|
|
116
|
-
const user = await userDb.getFirstUser();
|
|
117
|
-
return user ? { userId: user.id, username: user.username } : null;
|
|
118
|
-
} catch { return null; }
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
let token = null;
|
|
122
|
-
|
|
123
|
-
// 1. Parse cookie from upgrade request
|
|
124
|
-
const cookieHeader = request.headers?.cookie || '';
|
|
125
|
-
if (cookieHeader) {
|
|
126
|
-
const cookies = Object.fromEntries(
|
|
127
|
-
cookieHeader.split(';').map(c => {
|
|
128
|
-
const [k, ...v] = c.trim().split('=');
|
|
129
|
-
return [k, v.join('=')];
|
|
130
|
-
})
|
|
131
|
-
);
|
|
132
|
-
token = cookies.session || null;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// 2. Fallback: query param (legacy)
|
|
136
|
-
if (!token) {
|
|
137
|
-
try {
|
|
138
|
-
const url = new URL(request.url, 'http://localhost');
|
|
139
|
-
token = url.searchParams.get('token');
|
|
140
|
-
} catch { /* ignore */ }
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (!token) {
|
|
144
|
-
console.log('[WS-Auth] No token found in cookies or query params');
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Relay token (upfyn_ prefix) — validate against DB, not JWT
|
|
149
|
-
if (token.startsWith('upfyn_') || token.startsWith('rt_')) {
|
|
150
|
-
console.log(`[WS-Auth] Relay token detected: ${token.slice(0, 12)}...`);
|
|
151
|
-
try {
|
|
152
|
-
const tokenData = await relayTokensDb.validateToken(token);
|
|
153
|
-
if (tokenData) {
|
|
154
|
-
console.log(`[WS-Auth] Relay token VALID: userId=${tokenData.user_id} username=${tokenData.username}`);
|
|
155
|
-
return { userId: Number(tokenData.user_id), username: tokenData.username };
|
|
156
|
-
}
|
|
157
|
-
console.log('[WS-Auth] Relay token NOT FOUND in database');
|
|
158
|
-
} catch (err) {
|
|
159
|
-
console.error('[WS-Auth] Relay token validation error:', err.message);
|
|
160
|
-
}
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
const decoded = jwt.verify(token, JWT_SECRET);
|
|
166
|
-
// Validate against Turso — DB is source of truth
|
|
167
|
-
const user = await userDb.getUserById(decoded.userId);
|
|
168
|
-
if (!user) return null;
|
|
169
|
-
return { userId: user.id, username: user.username };
|
|
170
|
-
} catch { return null; }
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
export {
|
|
174
|
-
validateApiKey,
|
|
175
|
-
authenticateToken,
|
|
176
|
-
generateToken,
|
|
177
|
-
authenticateWebSocket,
|
|
178
|
-
setSessionCookie,
|
|
179
|
-
clearSessionCookie,
|
|
180
|
-
JWT_SECRET
|
|
181
|
-
};
|
package/server/openai-codex.js
DELETED
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenAI Codex SDK Integration
|
|
3
|
-
* =============================
|
|
4
|
-
*
|
|
5
|
-
* This module provides integration with the OpenAI Codex SDK for non-interactive
|
|
6
|
-
* chat sessions. It mirrors the pattern used in claude-sdk.js for consistency.
|
|
7
|
-
*
|
|
8
|
-
* ## Usage
|
|
9
|
-
*
|
|
10
|
-
* - queryCodex(command, options, ws) - Execute a prompt with streaming via WebSocket
|
|
11
|
-
* - abortCodexSession(sessionId) - Cancel an active session
|
|
12
|
-
* - isCodexSessionActive(sessionId) - Check if a session is running
|
|
13
|
-
* - getActiveCodexSessions() - List all active sessions
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { Codex } from '@openai/codex-sdk';
|
|
17
|
-
|
|
18
|
-
// Track active sessions
|
|
19
|
-
const activeCodexSessions = new Map();
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Transform Codex SDK event to WebSocket message format
|
|
23
|
-
* @param {object} event - SDK event
|
|
24
|
-
* @returns {object} - Transformed event for WebSocket
|
|
25
|
-
*/
|
|
26
|
-
function transformCodexEvent(event) {
|
|
27
|
-
// Map SDK event types to a consistent format
|
|
28
|
-
switch (event.type) {
|
|
29
|
-
case 'item.started':
|
|
30
|
-
case 'item.updated':
|
|
31
|
-
case 'item.completed':
|
|
32
|
-
const item = event.item;
|
|
33
|
-
if (!item) {
|
|
34
|
-
return { type: event.type, item: null };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Transform based on item type
|
|
38
|
-
switch (item.type) {
|
|
39
|
-
case 'agent_message':
|
|
40
|
-
return {
|
|
41
|
-
type: 'item',
|
|
42
|
-
itemType: 'agent_message',
|
|
43
|
-
message: {
|
|
44
|
-
role: 'assistant',
|
|
45
|
-
content: item.text
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
case 'reasoning':
|
|
50
|
-
return {
|
|
51
|
-
type: 'item',
|
|
52
|
-
itemType: 'reasoning',
|
|
53
|
-
message: {
|
|
54
|
-
role: 'assistant',
|
|
55
|
-
content: item.text,
|
|
56
|
-
isReasoning: true
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
case 'command_execution':
|
|
61
|
-
return {
|
|
62
|
-
type: 'item',
|
|
63
|
-
itemType: 'command_execution',
|
|
64
|
-
command: item.command,
|
|
65
|
-
output: item.aggregated_output,
|
|
66
|
-
exitCode: item.exit_code,
|
|
67
|
-
status: item.status
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
case 'file_change':
|
|
71
|
-
return {
|
|
72
|
-
type: 'item',
|
|
73
|
-
itemType: 'file_change',
|
|
74
|
-
changes: item.changes,
|
|
75
|
-
status: item.status
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
case 'mcp_tool_call':
|
|
79
|
-
return {
|
|
80
|
-
type: 'item',
|
|
81
|
-
itemType: 'mcp_tool_call',
|
|
82
|
-
server: item.server,
|
|
83
|
-
tool: item.tool,
|
|
84
|
-
arguments: item.arguments,
|
|
85
|
-
result: item.result,
|
|
86
|
-
error: item.error,
|
|
87
|
-
status: item.status
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
case 'web_search':
|
|
91
|
-
return {
|
|
92
|
-
type: 'item',
|
|
93
|
-
itemType: 'web_search',
|
|
94
|
-
query: item.query
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
case 'todo_list':
|
|
98
|
-
return {
|
|
99
|
-
type: 'item',
|
|
100
|
-
itemType: 'todo_list',
|
|
101
|
-
items: item.items
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
case 'error':
|
|
105
|
-
return {
|
|
106
|
-
type: 'item',
|
|
107
|
-
itemType: 'error',
|
|
108
|
-
message: {
|
|
109
|
-
role: 'error',
|
|
110
|
-
content: item.message
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
default:
|
|
115
|
-
return {
|
|
116
|
-
type: 'item',
|
|
117
|
-
itemType: item.type,
|
|
118
|
-
item: item
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
case 'turn.started':
|
|
123
|
-
return {
|
|
124
|
-
type: 'turn_started'
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
case 'turn.completed':
|
|
128
|
-
return {
|
|
129
|
-
type: 'turn_complete',
|
|
130
|
-
usage: event.usage
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
case 'turn.failed':
|
|
134
|
-
return {
|
|
135
|
-
type: 'turn_failed',
|
|
136
|
-
error: event.error
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
case 'thread.started':
|
|
140
|
-
return {
|
|
141
|
-
type: 'thread_started',
|
|
142
|
-
threadId: event.id
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
case 'error':
|
|
146
|
-
return {
|
|
147
|
-
type: 'error',
|
|
148
|
-
message: event.message
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
default:
|
|
152
|
-
return {
|
|
153
|
-
type: event.type,
|
|
154
|
-
data: event
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Map permission mode to Codex SDK options
|
|
161
|
-
* @param {string} permissionMode - 'default', 'acceptEdits', or 'bypassPermissions'
|
|
162
|
-
* @returns {object} - { sandboxMode, approvalPolicy }
|
|
163
|
-
*/
|
|
164
|
-
function mapPermissionModeToCodexOptions(permissionMode) {
|
|
165
|
-
switch (permissionMode) {
|
|
166
|
-
case 'acceptEdits':
|
|
167
|
-
return {
|
|
168
|
-
sandboxMode: 'workspace-write',
|
|
169
|
-
approvalPolicy: 'never'
|
|
170
|
-
};
|
|
171
|
-
case 'bypassPermissions':
|
|
172
|
-
return {
|
|
173
|
-
sandboxMode: 'danger-full-access',
|
|
174
|
-
approvalPolicy: 'never'
|
|
175
|
-
};
|
|
176
|
-
case 'default':
|
|
177
|
-
default:
|
|
178
|
-
return {
|
|
179
|
-
sandboxMode: 'workspace-write',
|
|
180
|
-
approvalPolicy: 'untrusted'
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Execute a Codex query with streaming
|
|
187
|
-
* @param {string} command - The prompt to send
|
|
188
|
-
* @param {object} options - Options including cwd, sessionId, model, permissionMode
|
|
189
|
-
* @param {WebSocket|object} ws - WebSocket connection or response writer
|
|
190
|
-
*/
|
|
191
|
-
export async function queryCodex(command, options = {}, ws) {
|
|
192
|
-
const {
|
|
193
|
-
sessionId,
|
|
194
|
-
cwd,
|
|
195
|
-
projectPath,
|
|
196
|
-
model,
|
|
197
|
-
permissionMode = 'default'
|
|
198
|
-
} = options;
|
|
199
|
-
|
|
200
|
-
const workingDirectory = cwd || projectPath || process.cwd();
|
|
201
|
-
const { sandboxMode, approvalPolicy } = mapPermissionModeToCodexOptions(permissionMode);
|
|
202
|
-
|
|
203
|
-
let codex;
|
|
204
|
-
let thread;
|
|
205
|
-
let currentSessionId = sessionId;
|
|
206
|
-
const abortController = new AbortController();
|
|
207
|
-
|
|
208
|
-
try {
|
|
209
|
-
// Initialize Codex SDK
|
|
210
|
-
codex = new Codex();
|
|
211
|
-
|
|
212
|
-
// Thread options with sandbox and approval settings
|
|
213
|
-
const threadOptions = {
|
|
214
|
-
workingDirectory,
|
|
215
|
-
skipGitRepoCheck: true,
|
|
216
|
-
sandboxMode,
|
|
217
|
-
approvalPolicy,
|
|
218
|
-
model
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
// Start or resume thread
|
|
222
|
-
if (sessionId) {
|
|
223
|
-
thread = codex.resumeThread(sessionId, threadOptions);
|
|
224
|
-
} else {
|
|
225
|
-
thread = codex.startThread(threadOptions);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Get the thread ID
|
|
229
|
-
currentSessionId = thread.id || sessionId || `codex-${Date.now()}`;
|
|
230
|
-
|
|
231
|
-
// Track the session
|
|
232
|
-
activeCodexSessions.set(currentSessionId, {
|
|
233
|
-
thread,
|
|
234
|
-
codex,
|
|
235
|
-
status: 'running',
|
|
236
|
-
abortController,
|
|
237
|
-
startedAt: new Date().toISOString()
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
// Send session created event
|
|
241
|
-
sendMessage(ws, {
|
|
242
|
-
type: 'session-created',
|
|
243
|
-
sessionId: currentSessionId,
|
|
244
|
-
provider: 'codex'
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
// Execute with streaming
|
|
248
|
-
const streamedTurn = await thread.runStreamed(command, {
|
|
249
|
-
signal: abortController.signal
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
for await (const event of streamedTurn.events) {
|
|
253
|
-
// Check if session was aborted
|
|
254
|
-
const session = activeCodexSessions.get(currentSessionId);
|
|
255
|
-
if (!session || session.status === 'aborted') {
|
|
256
|
-
break;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (event.type === 'item.started' || event.type === 'item.updated') {
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const transformed = transformCodexEvent(event);
|
|
264
|
-
|
|
265
|
-
sendMessage(ws, {
|
|
266
|
-
type: 'codex-response',
|
|
267
|
-
data: transformed,
|
|
268
|
-
sessionId: currentSessionId
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// Extract and send token usage if available (normalized to match Claude format)
|
|
272
|
-
if (event.type === 'turn.completed' && event.usage) {
|
|
273
|
-
const totalTokens = (event.usage.input_tokens || 0) + (event.usage.output_tokens || 0);
|
|
274
|
-
sendMessage(ws, {
|
|
275
|
-
type: 'token-budget',
|
|
276
|
-
data: {
|
|
277
|
-
used: totalTokens,
|
|
278
|
-
total: 200000 // Default context window for Codex models
|
|
279
|
-
},
|
|
280
|
-
sessionId: currentSessionId
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Send completion event
|
|
286
|
-
sendMessage(ws, {
|
|
287
|
-
type: 'codex-complete',
|
|
288
|
-
sessionId: currentSessionId,
|
|
289
|
-
actualSessionId: thread.id
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
} catch (error) {
|
|
293
|
-
const session = currentSessionId ? activeCodexSessions.get(currentSessionId) : null;
|
|
294
|
-
const wasAborted =
|
|
295
|
-
session?.status === 'aborted' ||
|
|
296
|
-
error?.name === 'AbortError' ||
|
|
297
|
-
String(error?.message || '').toLowerCase().includes('aborted');
|
|
298
|
-
|
|
299
|
-
if (!wasAborted) {
|
|
300
|
-
console.error('[Codex] Error:', error);
|
|
301
|
-
sendMessage(ws, {
|
|
302
|
-
type: 'codex-error',
|
|
303
|
-
error: error.message,
|
|
304
|
-
sessionId: currentSessionId
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
} finally {
|
|
309
|
-
// Update session status
|
|
310
|
-
if (currentSessionId) {
|
|
311
|
-
const session = activeCodexSessions.get(currentSessionId);
|
|
312
|
-
if (session) {
|
|
313
|
-
session.status = session.status === 'aborted' ? 'aborted' : 'completed';
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Abort an active Codex session
|
|
321
|
-
* @param {string} sessionId - Session ID to abort
|
|
322
|
-
* @returns {boolean} - Whether abort was successful
|
|
323
|
-
*/
|
|
324
|
-
export function abortCodexSession(sessionId) {
|
|
325
|
-
const session = activeCodexSessions.get(sessionId);
|
|
326
|
-
|
|
327
|
-
if (!session) {
|
|
328
|
-
return false;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
session.status = 'aborted';
|
|
332
|
-
try {
|
|
333
|
-
session.abortController?.abort();
|
|
334
|
-
} catch (error) {
|
|
335
|
-
console.warn(`[Codex] Failed to abort session ${sessionId}:`, error);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
return true;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Check if a session is active
|
|
343
|
-
* @param {string} sessionId - Session ID to check
|
|
344
|
-
* @returns {boolean} - Whether session is active
|
|
345
|
-
*/
|
|
346
|
-
export function isCodexSessionActive(sessionId) {
|
|
347
|
-
const session = activeCodexSessions.get(sessionId);
|
|
348
|
-
return session?.status === 'running';
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Get all active sessions
|
|
353
|
-
* @returns {Array} - Array of active session info
|
|
354
|
-
*/
|
|
355
|
-
export function getActiveCodexSessions() {
|
|
356
|
-
const sessions = [];
|
|
357
|
-
|
|
358
|
-
for (const [id, session] of activeCodexSessions.entries()) {
|
|
359
|
-
if (session.status === 'running') {
|
|
360
|
-
sessions.push({
|
|
361
|
-
id,
|
|
362
|
-
status: session.status,
|
|
363
|
-
startedAt: session.startedAt
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return sessions;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Helper to send message via WebSocket or writer
|
|
373
|
-
* @param {WebSocket|object} ws - WebSocket or response writer
|
|
374
|
-
* @param {object} data - Data to send
|
|
375
|
-
*/
|
|
376
|
-
function sendMessage(ws, data) {
|
|
377
|
-
try {
|
|
378
|
-
if (ws.isSSEStreamWriter || ws.isWebSocketWriter) {
|
|
379
|
-
// Writer handles stringification (SSEStreamWriter or WebSocketWriter)
|
|
380
|
-
ws.send(data);
|
|
381
|
-
} else if (typeof ws.send === 'function') {
|
|
382
|
-
// Raw WebSocket - stringify here
|
|
383
|
-
ws.send(JSON.stringify(data));
|
|
384
|
-
}
|
|
385
|
-
} catch (error) {
|
|
386
|
-
console.error('[Codex] Error sending message:', error);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Clean up old completed sessions periodically
|
|
391
|
-
setInterval(() => {
|
|
392
|
-
const now = Date.now();
|
|
393
|
-
const maxAge = 30 * 60 * 1000; // 30 minutes
|
|
394
|
-
|
|
395
|
-
for (const [id, session] of activeCodexSessions.entries()) {
|
|
396
|
-
if (session.status !== 'running') {
|
|
397
|
-
const startedAt = new Date(session.startedAt).getTime();
|
|
398
|
-
if (now - startedAt > maxAge) {
|
|
399
|
-
activeCodexSessions.delete(id);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}, 5 * 60 * 1000); // Every 5 minutes
|
package/server/openrouter.js
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenRouter Integration
|
|
3
|
-
*
|
|
4
|
-
* Lightweight wrapper for OpenRouter API, which provides access to
|
|
5
|
-
* hundreds of AI models (GPT-4, Claude, Gemini, Llama, Mistral, etc.)
|
|
6
|
-
* through a single API key and endpoint.
|
|
7
|
-
*
|
|
8
|
-
* Users provide their own OpenRouter API key via BYOK settings.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1';
|
|
12
|
-
|
|
13
|
-
// Popular models available on OpenRouter
|
|
14
|
-
export const OPENROUTER_MODELS = {
|
|
15
|
-
OPTIONS: [
|
|
16
|
-
{ value: 'anthropic/claude-sonnet-4', label: 'Claude Sonnet 4' },
|
|
17
|
-
{ value: 'anthropic/claude-opus-4', label: 'Claude Opus 4' },
|
|
18
|
-
{ value: 'openai/gpt-4o', label: 'GPT-4o' },
|
|
19
|
-
{ value: 'openai/o3', label: 'O3' },
|
|
20
|
-
{ value: 'google/gemini-2.5-pro', label: 'Gemini 2.5 Pro' },
|
|
21
|
-
{ value: 'meta-llama/llama-4-maverick', label: 'Llama 4 Maverick' },
|
|
22
|
-
{ value: 'mistralai/mistral-large', label: 'Mistral Large' },
|
|
23
|
-
{ value: 'deepseek/deepseek-r1', label: 'DeepSeek R1' },
|
|
24
|
-
],
|
|
25
|
-
DEFAULT: 'anthropic/claude-sonnet-4',
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Query OpenRouter API with streaming support
|
|
30
|
-
* @param {string} message - User message
|
|
31
|
-
* @param {Object} options - { model, apiKey, systemPrompt }
|
|
32
|
-
* @param {Object} writer - WebSocketWriter or SSEStreamWriter
|
|
33
|
-
*/
|
|
34
|
-
export async function queryOpenRouter(message, options = {}, writer) {
|
|
35
|
-
const {
|
|
36
|
-
model = OPENROUTER_MODELS.DEFAULT,
|
|
37
|
-
apiKey,
|
|
38
|
-
systemPrompt,
|
|
39
|
-
sessionId,
|
|
40
|
-
} = options;
|
|
41
|
-
|
|
42
|
-
if (!apiKey) {
|
|
43
|
-
writer.send({
|
|
44
|
-
type: 'error',
|
|
45
|
-
error: 'OpenRouter API key required. Add your key in Settings > AI Providers.',
|
|
46
|
-
});
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Generate session ID
|
|
51
|
-
const sid = sessionId || `or-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
52
|
-
writer.send({ type: 'session-created', sessionId: sid });
|
|
53
|
-
|
|
54
|
-
const messages = [];
|
|
55
|
-
if (systemPrompt) {
|
|
56
|
-
messages.push({ role: 'system', content: systemPrompt });
|
|
57
|
-
}
|
|
58
|
-
messages.push({ role: 'user', content: message });
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
const response = await fetch(`${OPENROUTER_BASE_URL}/chat/completions`, {
|
|
62
|
-
method: 'POST',
|
|
63
|
-
headers: {
|
|
64
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
65
|
-
'Content-Type': 'application/json',
|
|
66
|
-
'HTTP-Referer': 'https://cli.upfyn.com',
|
|
67
|
-
'X-Title': 'Upfyn-Code',
|
|
68
|
-
},
|
|
69
|
-
body: JSON.stringify({
|
|
70
|
-
model,
|
|
71
|
-
messages,
|
|
72
|
-
stream: true,
|
|
73
|
-
}),
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
if (!response.ok) {
|
|
77
|
-
const errBody = await response.text();
|
|
78
|
-
let errMsg = `OpenRouter API error (${response.status})`;
|
|
79
|
-
try {
|
|
80
|
-
const parsed = JSON.parse(errBody);
|
|
81
|
-
errMsg = parsed.error?.message || errMsg;
|
|
82
|
-
} catch {}
|
|
83
|
-
writer.send({ type: 'error', error: errMsg, sessionId: sid });
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Stream SSE response
|
|
88
|
-
const reader = response.body.getReader();
|
|
89
|
-
const decoder = new TextDecoder();
|
|
90
|
-
let buffer = '';
|
|
91
|
-
let fullContent = '';
|
|
92
|
-
|
|
93
|
-
while (true) {
|
|
94
|
-
const { done, value } = await reader.read();
|
|
95
|
-
if (done) break;
|
|
96
|
-
|
|
97
|
-
buffer += decoder.decode(value, { stream: true });
|
|
98
|
-
const lines = buffer.split('\n');
|
|
99
|
-
buffer = lines.pop() || '';
|
|
100
|
-
|
|
101
|
-
for (const line of lines) {
|
|
102
|
-
if (!line.startsWith('data: ')) continue;
|
|
103
|
-
const data = line.slice(6).trim();
|
|
104
|
-
if (data === '[DONE]') break;
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
const parsed = JSON.parse(data);
|
|
108
|
-
const delta = parsed.choices?.[0]?.delta?.content;
|
|
109
|
-
if (delta) {
|
|
110
|
-
fullContent += delta;
|
|
111
|
-
writer.send({
|
|
112
|
-
type: 'assistant',
|
|
113
|
-
content: delta,
|
|
114
|
-
sessionId: sid,
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
} catch {}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Send completion
|
|
122
|
-
writer.send({
|
|
123
|
-
type: 'result',
|
|
124
|
-
subtype: 'success',
|
|
125
|
-
sessionId: sid,
|
|
126
|
-
content: fullContent,
|
|
127
|
-
model,
|
|
128
|
-
provider: 'openrouter',
|
|
129
|
-
});
|
|
130
|
-
} catch (error) {
|
|
131
|
-
writer.send({
|
|
132
|
-
type: 'error',
|
|
133
|
-
error: `OpenRouter request failed: ${error.message}`,
|
|
134
|
-
sessionId: sid,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|