@shawnstack/quickforge 1.7.4 → 1.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/AgentProfilesPage-XNjuqJs7.js +1 -0
- package/dist/assets/ChatPanelHost-CuF-PZpc.js +292 -0
- package/dist/assets/CloudAccountSettingsPage-GJSoi6sO.js +1 -0
- package/dist/assets/PluginsPage-BQgp0qN-.js +1 -0
- package/dist/assets/ScheduledTasksPage-DdekATzq.js +2 -0
- package/dist/assets/{SettingsWorkspacePage-B53_eDf2.js → SettingsWorkspacePage-Cb54Ic91.js} +389 -378
- package/dist/assets/ShareLinksSettingsPage-C75WJpaF.js +1 -0
- package/dist/assets/SharedConversationPage-CjMtSQTW.js +1 -0
- package/dist/assets/{TerminalDock-Dgg-_ZmU.js → TerminalDock-D4zJBJc4.js} +2 -2
- package/dist/assets/WorkspaceInspector-D9kdnLDd.js +36 -0
- package/dist/assets/{abnfDiagram-VRR7QNED-Dy7veffk.js → abnfDiagram-VRR7QNED-CMhVoYUh.js} +1 -1
- package/dist/assets/{arc-Bo3p2lGy.js → arc-w21O0TLn.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-DMwFMHxq.js +1 -0
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-Cwx8qOvu.js → architectureDiagram-ZJ3FMSHR-C52qL6Hg.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-BiuaAOFa.js → blockDiagram-677ZJIJ3-CM3XbLol.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-BTcGfhK2.js → c4Diagram-LMCZKHZV-D7Bh1ui1.js} +1 -1
- package/dist/assets/channel-Duu1CuH9.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-B6rHWc4S.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-B9PuF_RF.js → chunk-32BRIVSS-xgrtbT8e.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-3zGItfIM.js → chunk-52WLFC77-CDbaHw80.js} +1 -1
- package/dist/assets/{chunk-5FCAYU7R-DQ66qlmt.js → chunk-5FCAYU7R-CcSvVbPc.js} +1 -1
- package/dist/assets/{chunk-5HE753X5-CLqTO-VF.js → chunk-5HE753X5-erFpzzgx.js} +1 -1
- package/dist/assets/{chunk-5JV3BV7I-Csxv1jr6.js → chunk-5JV3BV7I-rEhN9F6G.js} +1 -1
- package/dist/assets/{chunk-5TONJI2A-BCQttpBr.js → chunk-5TONJI2A-D8L-QTwD.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-DEWJGSTb.js → chunk-5VM5RSS4-CjlfMBbM.js} +1 -1
- package/dist/assets/{chunk-7BUUIJ7U-DNorm9MM.js → chunk-7BUUIJ7U-CCcsI9XE.js} +1 -1
- package/dist/assets/{chunk-BIQX33UG-DgvY_BOT.js → chunk-BIQX33UG-FSqFbmb3.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-DMvn9IPs.js → chunk-C7G6YPKG-ZMLUvfLy.js} +1 -1
- package/dist/assets/{chunk-CQNSW5MT-Dq2duUgr.js → chunk-CQNSW5MT-DZuBpk25.js} +1 -1
- package/dist/assets/{chunk-CYSBUYHQ-BovYyc4Y.js → chunk-CYSBUYHQ-CB26wsG0.js} +1 -1
- package/dist/assets/{chunk-EMLP6XTP-BMDOpuYN.js → chunk-EMLP6XTP-BbsM8oRw.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-6eP2UPOf.js → chunk-EX3LRPZG-BcQhaqt7.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-D4q-wTSy.js → chunk-FWX5IMBZ-DrnrdrSu.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-XjIcuzH7.js → chunk-HOUHSVGY-tdc-Z9eW.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-YBXbanq9.js → chunk-ICXQ74PX-Cd7igVGt.js} +1 -1
- package/dist/assets/{chunk-JG7HCLWE-CcN3uNXF.js → chunk-JG7HCLWE-iXXGW8nf.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-Cwg6AUAt.js → chunk-JWPE2WC7-BZ87mXEv.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-NNspcGIU.js → chunk-MOJQB5TN-CLTjV5ov.js} +1 -1
- package/dist/assets/{chunk-MOZMSUNE-pMJ_jMos.js → chunk-MOZMSUNE-B3SCBrRH.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-CJy-3Ldv.js → chunk-OGEWGWER-TgvkXkFX.js} +1 -1
- package/dist/assets/{chunk-OSBZ3O6U-D7cLHHOA.js → chunk-OSBZ3O6U-B9E2E5v1.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-D5YBi1Pi.js → chunk-PUDLZKDR-C5GMVYup.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-DYmqQdIf.js → chunk-Q4XR5HBZ-fo5-HsqY.js} +1 -1
- package/dist/assets/{chunk-QBLGF6JB-Bfcx00X0.js → chunk-QBLGF6JB-Dwfg8sKc.js} +1 -1
- package/dist/assets/{chunk-R7FJI6CG-oXzadpZY.js → chunk-R7FJI6CG-DpBrv4ni.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-Dt3-8yUh.js → chunk-RYQCIY6F-DzzmTl11.js} +1 -1
- package/dist/assets/{chunk-U6XO7XAA-B55eSvpx.js → chunk-U6XO7XAA-Dw6aviKa.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-Btk4JwCt.js → chunk-V7JOEXUC-CEDazV6w.js} +1 -1
- package/dist/assets/chunk-VAUOI2AC-CWYjg8zE.js +1 -0
- package/dist/assets/{chunk-VR4S4FIN-DgaSiVOY.js → chunk-VR4S4FIN-C_-eEj-9.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-rRDZ_Tta.js → chunk-WYO6CB5R-NF-MsJJR.js} +3 -3
- package/dist/assets/chunk-XXDRQBXY-DzTwx8ZH.js +1 -0
- package/dist/assets/{chunk-YOTPTUD7-BnFxj5o4.js → chunk-YOTPTUD7-DqzqtdII.js} +1 -1
- package/dist/assets/{chunk-ZGVPDNZ5-CrnlPlmJ.js → chunk-ZGVPDNZ5-CRrAYi4g.js} +1 -1
- package/dist/assets/{chunk-ZIRB5QZD-BYTiATIH.js → chunk-ZIRB5QZD-Dc1s5bLM.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-Cc63fLWP.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-CoXU0qT6.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-BORKJRwF.js → cose-bilkent-JH36ORCC-cL247pHg.js} +1 -1
- package/dist/assets/cynefin-VYW2F7L2-C8yCvH_o.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-BXKeaSl8.js → cynefinDiagram-TSTJHNR4-D4sZ7LYI.js} +1 -1
- package/dist/assets/{dagre-CrA0XVwJ.js → dagre-DmHb3Ctt.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-ouq_lA1a.js → dagre-VKFMJZFB-BlsnSh-Q.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-BxR5kUiQ.js → diagram-FQU43EPY-DW1yQiRO.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-DWonk1sL.js → diagram-G47NLZAW-BTUAsFjb.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-fmV_wV5e.js → diagram-NH7WQ7WH-BcaomqSd.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-DvOSRRiV.js → diagram-OA4YK3LP-DDLX7Xwr.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-Bp-xb3uS.js → diagram-WEI45ONY-CIave0An.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-BTlnwneA.js → ebnfDiagram-CCIWWBDH-Cr1Y6lPu.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-C3fFWGCm.js → erDiagram-Q63AITRT-D3EgiVVH.js} +1 -1
- package/dist/assets/{esm-oa69b5u4.js → esm-Df4edDpj.js} +2 -2
- package/dist/assets/eventmodeling-45OFAUF4-DO1vRTM0.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-BS7KXcqn.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-DzAiojNi.js → ganttDiagram-NO4QXBWP-B0oyDIqK.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-m5F5ef09.js +1 -0
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-Cv57oNEA.js → gitGraphDiagram-IHSO6WYX-uQtr6IqK.js} +1 -1
- package/dist/assets/icons-CXPP1fFT.js +1 -0
- package/dist/assets/index-_ZhK6bvp.js +65 -0
- package/dist/assets/index-iVV2bP_f.css +3 -0
- package/dist/assets/info-DKCQHKI2-CZxPlQJ0.js +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-B5Z_O3Ro.js → infoDiagram-FWYZ7A6U-B_GzlLIM.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-CKTK_TKV.js → ishikawaDiagram-FXEZZL3T-CKQIsSdU.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-Cm9k4ywW.js → journeyDiagram-5HDEW3XC-DrrvzZL1.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-DzNteLwx.js → kanban-definition-HUTT4EX6-CJQRTdXz.js} +1 -1
- package/dist/assets/{line-BG78c_WJ.js → line-B4DxX7tC.js} +1 -1
- package/dist/assets/{linear-1X20_EGu.js → linear-U-nKUhyW.js} +1 -1
- package/dist/assets/{map-CCI8WZkS.js → map-BneTM-ri.js} +1 -1
- package/dist/assets/{mcp-servers-dialog-GXXD9K0U.js → mcp-servers-dialog-BUSPvaqI.js} +2 -2
- package/dist/assets/{mermaid-parser.core-Bb8HI_OS.js → mermaid-parser.core-BlKWrEdZ.js} +2 -2
- package/dist/assets/{mermaid-renderer-PIaNiXAz.js → mermaid-renderer-jEbqFaTe.js} +2 -2
- package/dist/assets/{mermaid.core-DUHOIwL5.js → mermaid.core-DWpShXCs.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-Tnbszo7b.js → mindmap-definition-LN4V7U3C-CEVwZD78.js} +1 -1
- package/dist/assets/model-identity-CgA6_Pbm.js +1 -0
- package/dist/assets/model-reference-o41fJ6jQ.js +1 -0
- package/dist/assets/{monaco-BVtCspYZ.js → monaco-D6OhXgjg.js} +1 -1
- package/dist/assets/{ordinal-v4prQcuq.js → ordinal-B2r5L1v4.js} +1 -1
- package/dist/assets/{otel-D1LswXpM.js → otel-BFnphzjt.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-zIioIZVN.js +1 -0
- package/dist/assets/{pegDiagram-2B236MQR-CX824Kd0.js → pegDiagram-2B236MQR-CBLkBdnQ.js} +1 -1
- package/dist/assets/{pi-ai-DAJlO9ZB.js → pi-ai-Cr2ee4a0.js} +2 -2
- package/dist/assets/{pi-web-ui-B-RJSxjR.js → pi-web-ui-DV5jKjSg.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-GGWijBvB.js +1 -0
- package/dist/assets/{pieDiagram-ENE6RG2P-BT04VWPQ.js → pieDiagram-ENE6RG2P-8tCnwtWS.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-BvOZxkBr.js → quadrantDiagram-ABIIQ3AL-CWd28cHF.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-yvwFH3RV.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-BVadlswB.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-B7R-p_2-.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-BnbJxPug.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6-Ci3DcEuQ.js +1 -0
- package/dist/assets/{railroadDiagram-RFXS5EU6-DnTU--BV.js → railroadDiagram-RFXS5EU6-CI4ouOBs.js} +1 -1
- package/dist/assets/{react-vendor-eWH0QhNU.js → react-vendor-1KC5PyRx.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE--MZX_yEv.js → requirementDiagram-TGXJPOKE-DtFz7sw4.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-SEZkHhYn.js → sankeyDiagram-HTMAVEWB--VeOwwk9.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-BhC2OHiz.js → sequenceDiagram-DBY2YBRQ-B8AHk5Oe.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-Ci5LeToV.js → sizeCapture-X5ZJPWSS-vV_ArM8D.js} +1 -1
- package/dist/assets/{skills-dialog-DDh1h8eH.js → skills-dialog-BThnIUBs.js} +3 -3
- package/dist/assets/{src-CovulcRC.js → src-PYsDme0X.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-DKK5tFT4.js → stateDiagram-2N3HPSRC-etXnJ8X8.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-Dms3pICj.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-DxxGCk67.js → swimlanes-5IMT3BWC-pabIKAmn.js} +2 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-B14aWjv6.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-BPXB3Sue.js → timeline-definition-FHXFAJF6-C55MKm3L.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-Cru9B2zv.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-DNOVHFaJ.js +1 -0
- package/dist/assets/{vennDiagram-L72KCM5P-CxcBCKye.js → vennDiagram-L72KCM5P-CgNf1Q9W.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-E3m1BOJQ.js +1 -0
- package/dist/assets/{wardleyDiagram-EHGQE667-DEL6Yes5.js → wardleyDiagram-EHGQE667-Drt5ytec.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-N8cPbcjC.js → xychartDiagram-FW5EYKEG-hhoEo_Ar.js} +1 -1
- package/dist/index.html +9 -8
- package/package.json +3 -1
- package/plugins/{openai-documents → documents}/plugin.json +2 -2
- package/plugins/{openai-presentations → presentations}/plugin.json +2 -2
- package/plugins/{openai-spreadsheets → spreadsheets}/plugin.json +2 -2
- package/server/acp/server.mjs +36 -26
- package/server/agent-manager.mjs +130 -28
- package/server/agent-profile-files.mjs +8 -4
- package/server/agent-profile-schema.mjs +47 -23
- package/server/agent-profiles.mjs +1 -1
- package/server/ai-http-logger.mjs +98 -12
- package/server/cloud/chat-idempotency-store.mjs +96 -0
- package/server/cloud/client.mjs +102 -0
- package/server/cloud/config.mjs +53 -0
- package/server/cloud/credential-store.mjs +193 -0
- package/server/cloud/identity.mjs +355 -0
- package/server/cloud/index.mjs +7 -0
- package/server/cloud/models.mjs +80 -0
- package/server/cloud/runtime.mjs +129 -0
- package/server/cloud/service-config.mjs +77 -0
- package/server/index.mjs +28 -11
- package/server/model-catalog.mjs +262 -0
- package/server/plugins/registry.mjs +29 -5
- package/server/routes/agent-profiles.mjs +41 -7
- package/server/routes/agent.mjs +20 -11
- package/server/routes/backup.mjs +7 -1
- package/server/routes/cloud.mjs +227 -0
- package/server/routes/models.mjs +7 -1
- package/server/routes/scheduled-tasks.mjs +38 -8
- package/server/routes/shared-conversation.mjs +62 -58
- package/server/routes/shares.mjs +76 -1
- package/server/routes/storage.mjs +24 -1
- package/server/routes/workspace.mjs +6 -4
- package/server/share-store.mjs +168 -13
- package/server/skills.mjs +30 -3
- package/server/storage.mjs +7 -1
- package/server/subagents.mjs +3 -3
- package/server/tools/definitions.mjs +1 -1
- package/server/tools/index.mjs +2 -2
- package/server/utils/network.mjs +9 -0
- package/server/utils/response.mjs +8 -1
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/SKILL.md +485 -0
- package/skills/skill-creator/UPSTREAM.md +6 -0
- package/skills/skill-creator/agents/analyzer.md +274 -0
- package/skills/skill-creator/agents/comparator.md +202 -0
- package/skills/skill-creator/agents/grader.md +223 -0
- package/skills/skill-creator/assets/eval_review.html +146 -0
- package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/skill-creator/references/schemas.md +430 -0
- package/skills/skill-creator/scripts/__init__.py +0 -0
- package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/skill-creator/scripts/generate_report.py +326 -0
- package/skills/skill-creator/scripts/improve_description.py +247 -0
- package/skills/skill-creator/scripts/package_skill.py +136 -0
- package/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/skill-creator/scripts/run_eval.py +310 -0
- package/skills/skill-creator/scripts/run_loop.py +328 -0
- package/skills/skill-creator/scripts/utils.py +47 -0
- package/dist/assets/AgentProfilesPage-B84w-r9S.js +0 -1
- package/dist/assets/ChatPanelHost-CyO8fEoj.js +0 -288
- package/dist/assets/PluginsPage-D2862wul.js +0 -1
- package/dist/assets/ScheduledTasksPage-GqbiJDPX.js +0 -2
- package/dist/assets/SharedConversationPage-Cw2QFnbC.js +0 -1
- package/dist/assets/WorkspaceInspector-3uLiu6Bv.js +0 -13
- package/dist/assets/architecture-TIHT7OUA-7FQo5GbP.js +0 -1
- package/dist/assets/channel-DToA8d-4.js +0 -1
- package/dist/assets/chunk-2Q5K7J3B-D_Wxpk-T.js +0 -1
- package/dist/assets/chunk-VAUOI2AC-CIZseNLi.js +0 -1
- package/dist/assets/chunk-XXDRQBXY-Do-XYxat.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-Bu24Sy-k.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-DKhh5HSW.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-oyiAgYC4.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-YB_0MefX.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-Bc73xdBd.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-Bvp27L0n.js +0 -1
- package/dist/assets/icons-cTkqD_Gi.js +0 -1
- package/dist/assets/index-B48prgLL.js +0 -65
- package/dist/assets/index-DQhwHHer.css +0 -3
- package/dist/assets/info-DKCQHKI2-BsLOivgq.js +0 -1
- package/dist/assets/packet-7NZHBO7P-BKQkXRtk.js +0 -1
- package/dist/assets/pie-RZYD4A2V-BTiTN5Ge.js +0 -1
- package/dist/assets/radar-I7S5WNFK-BU-oVjKM.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-7H2hxNbL.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-Mx6wf6XQ.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-BgXSKhYy.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6-DTEl2rAC.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-D8x3mkni.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-C4nOBNp8.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-DOE6NSdf.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-Ca7QD_b4.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-D9O4RKRm.js +0 -1
- /package/dist/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
- /package/dist/assets/{chunk-KEIR6QF5-s7Qq-R3i.js → chunk-KEIR6QF5-jrz1pRUS.js} +0 -0
- /package/dist/assets/{chunk-Y2CYZVJY-BgISNSiw.js → chunk-Y2CYZVJY-D6N-CrJv.js} +0 -0
- /package/dist/assets/{cytoscape.esm-iekjSZ2n.js → cytoscape.esm-DyfqweF2.js} +0 -0
- /package/dist/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
- /package/dist/assets/{dist-zzfqk5hx.js → dist-CLg13NtV.js} +0 -0
- /package/dist/assets/{graphlib-CIz_CfxW.js → graphlib-DJkmx0Cq.js} +0 -0
- /package/dist/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
- /package/dist/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
- /package/dist/assets/{plugin-api-C8hMT5W6.js → plugin-api-Uu2xAB4d.js} +0 -0
- /package/dist/assets/{rough.esm-BMOtDaaS.js → rough.esm-Bud4g4fh.js} +0 -0
- /package/dist/assets/{web-RgX7H-OH.js → web-DoV8MSMn.js} +0 -0
- /package/dist/assets/{xterm-BFzZ1FH4.js → xterm-Bm_IbozX.js} +0 -0
- /package/plugins/{openai-documents → documents}/commands/document.md +0 -0
- /package/plugins/{openai-documents → documents}/skills/documents/SKILL.md +0 -0
- /package/plugins/{openai-presentations → presentations}/commands/presentation.md +0 -0
- /package/plugins/{openai-presentations → presentations}/skills/presentations/SKILL.md +0 -0
- /package/plugins/{openai-spreadsheets → spreadsheets}/commands/spreadsheet.md +0 -0
- /package/plugins/{openai-spreadsheets → spreadsheets}/skills/spreadsheets/SKILL.md +0 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
2
|
+
import { CloudApiError } from './client.mjs'
|
|
3
|
+
|
|
4
|
+
const SESSION_INVALID_CODES = new Set(['refresh_token_reused', 'installation_revoked', 'invalid_refresh_token'])
|
|
5
|
+
const DEVICE_PENDING_CODES = new Set(['authorization_pending', 'pending'])
|
|
6
|
+
const DEVICE_SLOW_DOWN_CODES = new Set(['slow_down'])
|
|
7
|
+
const DEVICE_DENIED_CODES = new Set(['access_denied', 'authorization_declined', 'denied'])
|
|
8
|
+
const DEVICE_EXPIRED_CODES = new Set(['expired_token', 'device_code_expired', 'expired'])
|
|
9
|
+
const DEVICE_NETWORK_CODES = new Set(['cloud_unavailable', 'service_unavailable', 'temporarily_unavailable'])
|
|
10
|
+
const DEVICE_NETWORK_CAUSE_CODES = new Set(['ECONNREFUSED', 'ECONNRESET', 'ENETDOWN', 'ENETUNREACH', 'EHOSTUNREACH', 'ETIMEDOUT', 'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_HEADERS_TIMEOUT', 'UND_ERR_SOCKET'])
|
|
11
|
+
const DEVICE_CLIENT_ID = 'quickforge-desktop'
|
|
12
|
+
const MODEL_CACHE_TTL_MS = 60_000
|
|
13
|
+
const MODEL_CACHE_HARD_TTL_MS = 5 * 60_000
|
|
14
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
15
|
+
|
|
16
|
+
function tokenExpiry(expiresIn) {
|
|
17
|
+
const seconds = Number(expiresIn)
|
|
18
|
+
return Date.now() + Math.max(1, Number.isFinite(seconds) ? seconds : 300) * 1000
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function publicAccountSummary(value) {
|
|
22
|
+
if (!value || typeof value !== 'object') return undefined
|
|
23
|
+
const summary = {}
|
|
24
|
+
for (const field of ['id', 'email', 'plan']) {
|
|
25
|
+
const candidate = value[field]
|
|
26
|
+
if (typeof candidate === 'string' && candidate) summary[field] = candidate
|
|
27
|
+
}
|
|
28
|
+
return Object.keys(summary).length ? summary : undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isDeviceFlowNetworkError(error) {
|
|
32
|
+
if (error instanceof CloudApiError) {
|
|
33
|
+
return error.retryable === true
|
|
34
|
+
|| error.status >= 500
|
|
35
|
+
|| DEVICE_NETWORK_CODES.has(error.code)
|
|
36
|
+
}
|
|
37
|
+
const causeCode = error?.cause?.code || error?.code
|
|
38
|
+
return error instanceof TypeError || DEVICE_NETWORK_CAUSE_CODES.has(causeCode)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function registrationHash(record, serviceUrl) {
|
|
42
|
+
return createHash('sha256').update(JSON.stringify({
|
|
43
|
+
installationName: record.installationName,
|
|
44
|
+
platform: record.platform,
|
|
45
|
+
clientVersion: record.clientVersion,
|
|
46
|
+
publicKey: record.publicKey,
|
|
47
|
+
serviceUrl,
|
|
48
|
+
})).digest('base64url')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class CloudIdentityManager {
|
|
52
|
+
constructor({ client, store, serviceUrl } = {}) {
|
|
53
|
+
if (!client || !store) throw new Error('CloudIdentityManager requires client and store.')
|
|
54
|
+
this.client = client
|
|
55
|
+
this.store = store
|
|
56
|
+
this.serviceUrl = String(serviceUrl || client.baseUrl?.href || '')
|
|
57
|
+
if (!this.serviceUrl) throw new Error('CloudIdentityManager requires a service URL.')
|
|
58
|
+
this.accessToken = undefined
|
|
59
|
+
this.accessExpiresAt = 0
|
|
60
|
+
this.refreshPromise = undefined
|
|
61
|
+
this.devicePollPromise = undefined
|
|
62
|
+
this.modelsCache = undefined
|
|
63
|
+
this.modelsCachedAt = 0
|
|
64
|
+
this.modelsPromise = undefined
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async status() {
|
|
68
|
+
let record = await this.store.read()
|
|
69
|
+
if (record.pendingDeviceFlow && Number(record.pendingDeviceFlow.expiresAt) <= Date.now()) {
|
|
70
|
+
record = await this.store.update((current) => ({ ...current, pendingDeviceFlow: undefined }))
|
|
71
|
+
}
|
|
72
|
+
const publicStatus = await this.store.readPublic()
|
|
73
|
+
const sessionServiceMismatch = Boolean(record.refreshToken && record.sessionCloudUrl !== this.serviceUrl)
|
|
74
|
+
const pendingServiceMismatch = Boolean(record.pendingDeviceFlow && record.pendingDeviceFlow.sessionCloudUrl !== this.serviceUrl)
|
|
75
|
+
return (sessionServiceMismatch || pendingServiceMismatch)
|
|
76
|
+
? { ...publicStatus, sessionServiceMismatch: true }
|
|
77
|
+
: publicStatus
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
assertSessionService(record) {
|
|
81
|
+
if (record.refreshToken && record.sessionCloudUrl !== this.serviceUrl) {
|
|
82
|
+
throw new CloudApiError('The saved QuickForge Cloud session belongs to a different or unknown service. Rebuild the local Cloud identity before continuing.', {
|
|
83
|
+
status: 409,
|
|
84
|
+
code: 'cloud_session_service_mismatch',
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
if (record.pendingDeviceFlow && record.pendingDeviceFlow.sessionCloudUrl !== this.serviceUrl) {
|
|
88
|
+
throw new CloudApiError('The pending QuickForge Cloud sign-in belongs to a different service. Cancel or rebuild the local Cloud identity before continuing.', {
|
|
89
|
+
status: 409,
|
|
90
|
+
code: 'cloud_session_service_mismatch',
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async startGuest({ signal } = {}) {
|
|
96
|
+
let record = await this.store.read()
|
|
97
|
+
this.assertSessionService(record)
|
|
98
|
+
if (record.rotateInstallationBeforeRegistration) {
|
|
99
|
+
record = await this.store.rotateInstallation()
|
|
100
|
+
} else {
|
|
101
|
+
record = await this.store.ensureInstallation()
|
|
102
|
+
}
|
|
103
|
+
const requestHash = registrationHash(record, this.serviceUrl)
|
|
104
|
+
let idempotencyKey = record.pendingRegistrationKey
|
|
105
|
+
if (!UUID_PATTERN.test(idempotencyKey || '') || record.pendingRegistrationHash !== requestHash) {
|
|
106
|
+
idempotencyKey = randomUUID()
|
|
107
|
+
await this.store.update((current) => ({
|
|
108
|
+
...current,
|
|
109
|
+
pendingRegistrationKey: idempotencyKey,
|
|
110
|
+
pendingRegistrationHash: requestHash,
|
|
111
|
+
}))
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const tokens = await this.client.registerGuest({
|
|
115
|
+
installationName: record.installationName,
|
|
116
|
+
platform: record.platform,
|
|
117
|
+
clientVersion: record.clientVersion,
|
|
118
|
+
publicKey: record.publicKey,
|
|
119
|
+
}, idempotencyKey, signal)
|
|
120
|
+
|
|
121
|
+
this.setAccess(tokens)
|
|
122
|
+
await this.store.update((current) => ({
|
|
123
|
+
...current,
|
|
124
|
+
mode: tokens.identityMode || 'guest',
|
|
125
|
+
installationId: tokens.installationId,
|
|
126
|
+
refreshToken: tokens.refreshToken,
|
|
127
|
+
sessionCloudUrl: this.serviceUrl,
|
|
128
|
+
pendingRegistrationKey: undefined,
|
|
129
|
+
pendingRegistrationHash: undefined,
|
|
130
|
+
pendingDeviceFlow: undefined,
|
|
131
|
+
}))
|
|
132
|
+
return this.getStatusWithRemoteSummary(signal)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async startDeviceFlow({ signal } = {}) {
|
|
136
|
+
let record = await this.store.read()
|
|
137
|
+
this.assertSessionService(record)
|
|
138
|
+
if (record.mode === 'account' && record.refreshToken) {
|
|
139
|
+
throw new CloudApiError('QuickForge Cloud is already connected to an account.', { status: 409, code: 'cloud_account_already_connected' })
|
|
140
|
+
}
|
|
141
|
+
if (record.mode === 'local') {
|
|
142
|
+
await this.startGuest({ signal })
|
|
143
|
+
record = await this.store.read()
|
|
144
|
+
}
|
|
145
|
+
if (!record.refreshToken || !record.installationId) {
|
|
146
|
+
throw new CloudApiError('QuickForge Cloud guest session is required before account sign-in.', { status: 409, code: 'cloud_not_connected' })
|
|
147
|
+
}
|
|
148
|
+
this.assertSessionService(record)
|
|
149
|
+
const accessToken = await this.access({ signal })
|
|
150
|
+
const authorization = await this.client.authorizeDevice(accessToken, record.installationId, DEVICE_CLIENT_ID, signal)
|
|
151
|
+
const expiresIn = Math.max(1, Number(authorization?.expiresIn) || 600)
|
|
152
|
+
const interval = Math.max(1, Number(authorization?.interval) || 5)
|
|
153
|
+
const pendingDeviceFlow = {
|
|
154
|
+
deviceCode: String(authorization?.deviceCode || ''),
|
|
155
|
+
userCode: String(authorization?.userCode || ''),
|
|
156
|
+
verificationUri: String(authorization?.verificationUri || ''),
|
|
157
|
+
verificationUriComplete: String(authorization?.verificationUriComplete || ''),
|
|
158
|
+
expiresAt: Date.now() + expiresIn * 1000,
|
|
159
|
+
interval,
|
|
160
|
+
status: 'pending',
|
|
161
|
+
sessionCloudUrl: this.serviceUrl,
|
|
162
|
+
}
|
|
163
|
+
if (!pendingDeviceFlow.deviceCode || !pendingDeviceFlow.userCode || !pendingDeviceFlow.verificationUri) {
|
|
164
|
+
throw new CloudApiError('QuickForge Cloud returned an invalid device authorization response.', { code: 'invalid_device_authorization_response' })
|
|
165
|
+
}
|
|
166
|
+
await this.store.update((current) => ({ ...current, pendingDeviceFlow }))
|
|
167
|
+
return this.status()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async pollDeviceFlow(options = {}) {
|
|
171
|
+
if (this.devicePollPromise) return this.devicePollPromise
|
|
172
|
+
this.devicePollPromise = this.pollDeviceFlowOnce(options)
|
|
173
|
+
.finally(() => { this.devicePollPromise = undefined })
|
|
174
|
+
return this.devicePollPromise
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async pollDeviceFlowOnce({ signal } = {}) {
|
|
178
|
+
const record = await this.store.read()
|
|
179
|
+
this.assertSessionService(record)
|
|
180
|
+
const pending = record.pendingDeviceFlow
|
|
181
|
+
if (!pending?.deviceCode) {
|
|
182
|
+
throw new CloudApiError('There is no pending QuickForge Cloud sign-in.', { status: 409, code: 'cloud_device_flow_not_pending' })
|
|
183
|
+
}
|
|
184
|
+
if (Number(pending.expiresAt) <= Date.now()) {
|
|
185
|
+
await this.store.update((current) => ({ ...current, pendingDeviceFlow: undefined }))
|
|
186
|
+
return { ...(await this.status()), deviceFlowResult: 'expired' }
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const tokens = await this.client.exchangeDeviceCode(pending.deviceCode, DEVICE_CLIENT_ID, signal)
|
|
190
|
+
if (!tokens?.refreshToken || !tokens?.accessToken) {
|
|
191
|
+
throw new CloudApiError('QuickForge Cloud returned an invalid account token response.', { code: 'invalid_token_response' })
|
|
192
|
+
}
|
|
193
|
+
this.setAccess(tokens)
|
|
194
|
+
this.modelsCache = undefined
|
|
195
|
+
this.modelsCachedAt = 0
|
|
196
|
+
this.modelsPromise = undefined
|
|
197
|
+
await this.store.update((current) => ({
|
|
198
|
+
...current,
|
|
199
|
+
mode: 'account',
|
|
200
|
+
installationId: tokens.installationId || current.installationId,
|
|
201
|
+
refreshToken: tokens.refreshToken,
|
|
202
|
+
sessionCloudUrl: this.serviceUrl,
|
|
203
|
+
pendingDeviceFlow: undefined,
|
|
204
|
+
account: undefined,
|
|
205
|
+
}))
|
|
206
|
+
return { ...(await this.getStatusWithRemoteSummary(signal)), deviceFlowResult: 'success' }
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (DEVICE_PENDING_CODES.has(error?.code)) return { ...(await this.status()), deviceFlowResult: 'pending' }
|
|
209
|
+
if (DEVICE_SLOW_DOWN_CODES.has(error?.code)) {
|
|
210
|
+
await this.store.update((current) => ({
|
|
211
|
+
...current,
|
|
212
|
+
pendingDeviceFlow: current.pendingDeviceFlow
|
|
213
|
+
? { ...current.pendingDeviceFlow, interval: Math.max(1, Number(current.pendingDeviceFlow.interval) || 5) + 5, status: 'slow_down' }
|
|
214
|
+
: undefined,
|
|
215
|
+
}))
|
|
216
|
+
return { ...(await this.status()), deviceFlowResult: 'slow_down' }
|
|
217
|
+
}
|
|
218
|
+
if (DEVICE_DENIED_CODES.has(error?.code) || DEVICE_EXPIRED_CODES.has(error?.code)) {
|
|
219
|
+
await this.store.update((current) => ({ ...current, pendingDeviceFlow: undefined }))
|
|
220
|
+
return { ...(await this.status()), deviceFlowResult: DEVICE_DENIED_CODES.has(error?.code) ? 'denied' : 'expired' }
|
|
221
|
+
}
|
|
222
|
+
if (isDeviceFlowNetworkError(error)) {
|
|
223
|
+
return { ...(await this.status()), deviceFlowResult: 'network' }
|
|
224
|
+
}
|
|
225
|
+
throw error
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async cancelDeviceFlow() {
|
|
230
|
+
const record = await this.store.read()
|
|
231
|
+
this.assertSessionService(record)
|
|
232
|
+
await this.store.update((current) => ({ ...current, pendingDeviceFlow: undefined }))
|
|
233
|
+
return this.status()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
setAccess(tokens) {
|
|
237
|
+
this.accessToken = tokens?.accessToken
|
|
238
|
+
this.accessExpiresAt = tokenExpiry(tokens?.expiresIn)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async clearInvalidSession({ rotateInstallationBeforeRegistration = false } = {}) {
|
|
242
|
+
this.clearMemory()
|
|
243
|
+
await this.store.clearSession({ rotateInstallationBeforeRegistration })
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
clearMemory() {
|
|
247
|
+
this.accessToken = undefined
|
|
248
|
+
this.accessExpiresAt = 0
|
|
249
|
+
this.refreshPromise = undefined
|
|
250
|
+
this.devicePollPromise = undefined
|
|
251
|
+
this.modelsCache = undefined
|
|
252
|
+
this.modelsCachedAt = 0
|
|
253
|
+
this.modelsPromise = undefined
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async resetIdentity() {
|
|
257
|
+
this.clearMemory()
|
|
258
|
+
await this.store.rotateInstallation()
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async refresh({ signal } = {}) {
|
|
262
|
+
if (this.refreshPromise) return this.refreshPromise
|
|
263
|
+
this.refreshPromise = (async () => {
|
|
264
|
+
const record = await this.store.read()
|
|
265
|
+
if (!record.refreshToken) throw new CloudApiError('QuickForge Cloud is not connected.', { status: 401, code: 'cloud_not_connected' })
|
|
266
|
+
this.assertSessionService(record)
|
|
267
|
+
try {
|
|
268
|
+
const tokens = await this.client.refresh(record.refreshToken, signal)
|
|
269
|
+
if (!tokens?.refreshToken) throw new CloudApiError('QuickForge Cloud did not rotate the refresh token.', { code: 'invalid_token_response' })
|
|
270
|
+
this.setAccess(tokens)
|
|
271
|
+
await this.store.update((current) => ({
|
|
272
|
+
...current,
|
|
273
|
+
mode: tokens.identityMode || current.mode || 'guest',
|
|
274
|
+
installationId: tokens.installationId || current.installationId,
|
|
275
|
+
refreshToken: tokens.refreshToken,
|
|
276
|
+
sessionCloudUrl: this.serviceUrl,
|
|
277
|
+
}))
|
|
278
|
+
return this.accessToken
|
|
279
|
+
} catch (error) {
|
|
280
|
+
if (SESSION_INVALID_CODES.has(error?.code)) {
|
|
281
|
+
await this.clearInvalidSession({ rotateInstallationBeforeRegistration: true })
|
|
282
|
+
}
|
|
283
|
+
throw error
|
|
284
|
+
}
|
|
285
|
+
})().finally(() => { this.refreshPromise = undefined })
|
|
286
|
+
return this.refreshPromise
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async access({ signal, forceRefresh = false } = {}) {
|
|
290
|
+
if (!forceRefresh && this.accessToken && this.accessExpiresAt > Date.now() + 30_000) return this.accessToken
|
|
291
|
+
return this.refresh({ signal })
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async withAccessToken(operation, { signal } = {}) {
|
|
295
|
+
let token = await this.access({ signal })
|
|
296
|
+
try {
|
|
297
|
+
return await operation(token)
|
|
298
|
+
} catch (error) {
|
|
299
|
+
if (!(error instanceof CloudApiError) || error.status !== 401) throw error
|
|
300
|
+
token = await this.access({ signal, forceRefresh: true })
|
|
301
|
+
return operation(token)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async getStatusWithRemoteSummary(signal) {
|
|
306
|
+
const local = await this.status()
|
|
307
|
+
if (!local.hasSession) return local
|
|
308
|
+
try {
|
|
309
|
+
const [remoteAccount, usage] = await Promise.all([this.me(signal), this.usage(signal)])
|
|
310
|
+
const account = publicAccountSummary(remoteAccount)
|
|
311
|
+
await this.store.update((record) => ({ ...record, account }))
|
|
312
|
+
return { ...(await this.status()), account, usage, cloudAvailable: true }
|
|
313
|
+
} catch (error) {
|
|
314
|
+
return { ...local, cloudAvailable: false, cloudError: error?.code || 'cloud_unavailable' }
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
me(signal) { return this.withAccessToken((token) => this.client.me(token, signal), { signal }) }
|
|
319
|
+
usage(signal) { return this.withAccessToken((token) => this.client.usage(token, signal), { signal }) }
|
|
320
|
+
installations(signal) { return this.withAccessToken((token) => this.client.installations(token, signal), { signal }) }
|
|
321
|
+
revokeInstallation(id, signal) {
|
|
322
|
+
return this.withAccessToken((token) => this.client.revokeInstallation(token, id, signal), { signal })
|
|
323
|
+
}
|
|
324
|
+
async logout({ signal } = {}) {
|
|
325
|
+
const record = await this.store.read()
|
|
326
|
+
if (!record.refreshToken || !record.installationId) {
|
|
327
|
+
await this.clearInvalidSession()
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
this.assertSessionService(record)
|
|
331
|
+
await this.revokeInstallation(record.installationId, signal)
|
|
332
|
+
this.clearMemory()
|
|
333
|
+
await this.store.clearSession({ rotateInstallationBeforeRegistration: true })
|
|
334
|
+
}
|
|
335
|
+
async models(signal, { refresh = false, allowStale = true } = {}) {
|
|
336
|
+
const age = Date.now() - this.modelsCachedAt
|
|
337
|
+
if (!refresh && this.modelsCache && age < MODEL_CACHE_TTL_MS) return this.modelsCache
|
|
338
|
+
if (this.modelsPromise) return this.modelsPromise
|
|
339
|
+
this.modelsPromise = this.withAccessToken((token) => this.client.models(token, signal), { signal })
|
|
340
|
+
.then((response) => {
|
|
341
|
+
this.modelsCache = Array.isArray(response?.items) ? response.items : []
|
|
342
|
+
this.modelsCachedAt = Date.now()
|
|
343
|
+
return this.modelsCache
|
|
344
|
+
})
|
|
345
|
+
.catch((error) => {
|
|
346
|
+
if (allowStale && this.modelsCache && age < MODEL_CACHE_HARD_TTL_MS) return this.modelsCache
|
|
347
|
+
throw error
|
|
348
|
+
})
|
|
349
|
+
.finally(() => { this.modelsPromise = undefined })
|
|
350
|
+
return this.modelsPromise
|
|
351
|
+
}
|
|
352
|
+
chat(payload, idempotencyKey, signal) {
|
|
353
|
+
return this.withAccessToken((token) => this.client.chat(token, payload, idempotencyKey, signal), { signal })
|
|
354
|
+
}
|
|
355
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { readCloudConfig, parseCloudBaseUrl, cloudEndpoint } from './config.mjs'
|
|
2
|
+
export { readCloudServiceConfig, saveCloudServiceConfig, publicCloudServiceConfig, DEFAULT_CLOUD_URL } from './service-config.mjs'
|
|
3
|
+
export { CloudClient, CloudApiError } from './client.mjs'
|
|
4
|
+
export { createCloudCredentialStore, publicCloudCredentialRecord } from './credential-store.mjs'
|
|
5
|
+
export { CloudIdentityManager } from './identity.mjs'
|
|
6
|
+
export { ManagedCloudModels, isManagedCloudModel, toPublicCloudModel, QUICKFORGE_CLOUD_PROVIDER } from './models.mjs'
|
|
7
|
+
export { createCloudRuntime, getCloudRuntime, invalidateCloudRuntime, resolveManagedCloudProvider } from './runtime.mjs'
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
const CLOUD_PROVIDER = 'quickforge-cloud'
|
|
4
|
+
const CLOUD_SOURCE = 'cloud'
|
|
5
|
+
|
|
6
|
+
export function toPublicCloudModel(model) {
|
|
7
|
+
const id = String(model?.id || '').trim()
|
|
8
|
+
if (!id) throw new Error('Cloud model is missing an id.')
|
|
9
|
+
const capabilities = model.capabilities || {}
|
|
10
|
+
return {
|
|
11
|
+
id,
|
|
12
|
+
name: String(model.name || id),
|
|
13
|
+
provider: CLOUD_PROVIDER,
|
|
14
|
+
api: 'openai-completions',
|
|
15
|
+
baseUrl: `quickforge://cloud/${encodeURIComponent(id)}`,
|
|
16
|
+
reasoning: Boolean(capabilities.reasoning),
|
|
17
|
+
input: capabilities.vision ? ['text', 'image'] : ['text'],
|
|
18
|
+
contextWindow: Number(model.contextWindow || 128_000),
|
|
19
|
+
maxTokens: Number(model.maxTokens || 8_192),
|
|
20
|
+
quickforgeModelSource: CLOUD_SOURCE,
|
|
21
|
+
quickforgeCatalogId: id,
|
|
22
|
+
quickforgeCapabilities: {
|
|
23
|
+
tools: Boolean(capabilities.tools),
|
|
24
|
+
vision: Boolean(capabilities.vision),
|
|
25
|
+
reasoning: Boolean(capabilities.reasoning),
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isManagedCloudModel(model) {
|
|
31
|
+
return model?.provider === CLOUD_PROVIDER && model?.quickforgeModelSource === CLOUD_SOURCE && typeof model?.quickforgeCatalogId === 'string'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class ManagedCloudModels {
|
|
35
|
+
constructor({ identity } = {}) {
|
|
36
|
+
if (!identity) throw new Error('ManagedCloudModels requires an identity manager.')
|
|
37
|
+
this.identity = identity
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async list(signal, options) {
|
|
41
|
+
return (await this.identity.models(signal, options))
|
|
42
|
+
.filter((model) => model?.available !== false)
|
|
43
|
+
.map(toPublicCloudModel)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async resolve(model, signal) {
|
|
47
|
+
if (!isManagedCloudModel(model)) return undefined
|
|
48
|
+
const catalogId = model.quickforgeCatalogId
|
|
49
|
+
let available = await this.identity.models(signal)
|
|
50
|
+
let entry = available.find((item) => item?.id === catalogId && item?.available !== false)
|
|
51
|
+
if (!entry) {
|
|
52
|
+
available = await this.identity.models(signal, { refresh: true, allowStale: false })
|
|
53
|
+
entry = available.find((item) => item?.id === catalogId && item?.available !== false)
|
|
54
|
+
}
|
|
55
|
+
if (!entry) {
|
|
56
|
+
const error = new Error(`QuickForge Cloud model is unavailable: ${catalogId}`)
|
|
57
|
+
error.code = 'cloud_model_unavailable'
|
|
58
|
+
throw error
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
publicModel: toPublicCloudModel(entry),
|
|
62
|
+
catalogId,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async chat(model, payload, signal) {
|
|
67
|
+
const resolved = await this.resolve(model, signal)
|
|
68
|
+
if (!resolved) throw new Error('Model is not managed by QuickForge Cloud.')
|
|
69
|
+
const request = {
|
|
70
|
+
...payload,
|
|
71
|
+
model: resolved.catalogId,
|
|
72
|
+
}
|
|
73
|
+
delete request.baseUrl
|
|
74
|
+
delete request.headers
|
|
75
|
+
delete request.apiKey
|
|
76
|
+
return this.identity.chat(request, randomUUID(), signal)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const QUICKFORGE_CLOUD_PROVIDER = CLOUD_PROVIDER
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { CloudClient } from './client.mjs'
|
|
2
|
+
import { readCloudConfig, cloudEndpoint } from './config.mjs'
|
|
3
|
+
import { createCloudCredentialStore } from './credential-store.mjs'
|
|
4
|
+
import { CloudIdentityManager } from './identity.mjs'
|
|
5
|
+
import { ManagedCloudModels, isManagedCloudModel } from './models.mjs'
|
|
6
|
+
import { readCloudServiceConfig } from './service-config.mjs'
|
|
7
|
+
|
|
8
|
+
let singleton
|
|
9
|
+
let singletonKey
|
|
10
|
+
let singletonPromise
|
|
11
|
+
let runtimeOverride = false
|
|
12
|
+
let runtimeGeneration = 0
|
|
13
|
+
let runtimeFactory = createCloudRuntime
|
|
14
|
+
|
|
15
|
+
function runtimeKey(config, generation = runtimeGeneration) {
|
|
16
|
+
return `${generation}:${config?.baseUrl?.href || ''}:${config?.timeoutMs || ''}`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createCloudRuntime({ config = readCloudConfig(), store, client } = {}) {
|
|
20
|
+
if (!config?.enabled) return { enabled: false, config }
|
|
21
|
+
const credentialStore = store || createCloudCredentialStore({ clientVersion: process.env.npm_package_version || 'unknown' })
|
|
22
|
+
const cloudClient = client || new CloudClient(config)
|
|
23
|
+
const identity = new CloudIdentityManager({ client: cloudClient, store: credentialStore, serviceUrl: config.baseUrl.href })
|
|
24
|
+
const models = new ManagedCloudModels({ identity })
|
|
25
|
+
return { enabled: true, config, store: credentialStore, client: cloudClient, identity, models }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function resolvedRuntimeConfig() {
|
|
29
|
+
const service = await readCloudServiceConfig()
|
|
30
|
+
const timeoutValue = Number(process.env.QUICKFORGE_CLOUD_TIMEOUT_MS || 10_000)
|
|
31
|
+
return {
|
|
32
|
+
enabled: Boolean(service.baseUrl),
|
|
33
|
+
baseUrl: service.baseUrl,
|
|
34
|
+
cloudUrl: service.cloudUrl,
|
|
35
|
+
source: service.source,
|
|
36
|
+
saved: service.saved,
|
|
37
|
+
timeoutMs: Number.isFinite(timeoutValue) ? Math.min(60_000, Math.max(1_000, timeoutValue)) : 10_000,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function getCloudRuntime() {
|
|
42
|
+
if (runtimeOverride) return singleton
|
|
43
|
+
const generation = runtimeGeneration
|
|
44
|
+
const config = await resolvedRuntimeConfig()
|
|
45
|
+
if (runtimeOverride) return singleton
|
|
46
|
+
if (generation !== runtimeGeneration) return getCloudRuntime()
|
|
47
|
+
const key = runtimeKey(config, generation)
|
|
48
|
+
if (singleton && singletonKey === key) return singleton
|
|
49
|
+
if (singletonPromise && singletonKey === key) return singletonPromise
|
|
50
|
+
singletonKey = key
|
|
51
|
+
const promise = Promise.resolve(runtimeFactory({ config })).then((runtime) => {
|
|
52
|
+
if (
|
|
53
|
+
!runtimeOverride
|
|
54
|
+
&& generation === runtimeGeneration
|
|
55
|
+
&& singletonKey === key
|
|
56
|
+
&& singletonPromise === promise
|
|
57
|
+
) {
|
|
58
|
+
singleton = runtime
|
|
59
|
+
}
|
|
60
|
+
return runtime
|
|
61
|
+
}).finally(() => {
|
|
62
|
+
if (singletonPromise === promise) singletonPromise = undefined
|
|
63
|
+
})
|
|
64
|
+
singletonPromise = promise
|
|
65
|
+
return promise
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function invalidateCloudRuntime() {
|
|
69
|
+
runtimeGeneration++
|
|
70
|
+
runtimeOverride = false
|
|
71
|
+
singleton = undefined
|
|
72
|
+
singletonKey = undefined
|
|
73
|
+
singletonPromise = undefined
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function resolveManagedCloudProvider(model, signal) {
|
|
77
|
+
if (!isManagedCloudModel(model)) return undefined
|
|
78
|
+
const runtime = await getCloudRuntime()
|
|
79
|
+
if (!runtime.enabled) throw new Error('QuickForge Cloud is not configured.')
|
|
80
|
+
const resolved = await runtime.models.resolve(model, signal)
|
|
81
|
+
const accessToken = await runtime.identity.access({ signal })
|
|
82
|
+
return {
|
|
83
|
+
model: {
|
|
84
|
+
...resolved.publicModel,
|
|
85
|
+
id: resolved.catalogId,
|
|
86
|
+
name: resolved.publicModel.name || resolved.catalogId,
|
|
87
|
+
provider: 'quickforge-cloud',
|
|
88
|
+
api: 'openai-completions',
|
|
89
|
+
baseUrl: cloudEndpoint(runtime.config.baseUrl, 'v1/').href.replace(/\/$/, ''),
|
|
90
|
+
headers: undefined,
|
|
91
|
+
// The Cloud gateway implements the conservative OpenAI-compatible request
|
|
92
|
+
// shape. Disable newer OpenAI-only fields that strict gateways may reject.
|
|
93
|
+
compat: {
|
|
94
|
+
supportsStore: false,
|
|
95
|
+
supportsDeveloperRole: false,
|
|
96
|
+
supportsReasoningEffort: false,
|
|
97
|
+
supportsUsageInStreaming: false,
|
|
98
|
+
supportsStrictMode: false,
|
|
99
|
+
maxTokensField: 'max_tokens',
|
|
100
|
+
},
|
|
101
|
+
// The cloud catalog does not carry per-model USD prices (billing is done
|
|
102
|
+
// server-side in credits). pi-ai's calculateCost() reads model.cost.*
|
|
103
|
+
// unconditionally, so provide a zero cost table to avoid throwing when
|
|
104
|
+
// parsing streamed usage chunks.
|
|
105
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
106
|
+
},
|
|
107
|
+
apiKey: accessToken,
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function resetCloudRuntimeForTests() {
|
|
112
|
+
runtimeGeneration = 0
|
|
113
|
+
runtimeOverride = false
|
|
114
|
+
runtimeFactory = createCloudRuntime
|
|
115
|
+
singleton = undefined
|
|
116
|
+
singletonKey = undefined
|
|
117
|
+
singletonPromise = undefined
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function setCloudRuntimeFactoryForTests(factory) {
|
|
121
|
+
runtimeFactory = factory
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function setCloudRuntimeForTests(runtime) {
|
|
125
|
+
runtimeOverride = true
|
|
126
|
+
singleton = runtime
|
|
127
|
+
singletonKey = runtimeKey(runtime?.config)
|
|
128
|
+
singletonPromise = undefined
|
|
129
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { atomicUpdate, readStore } from '../storage.mjs'
|
|
2
|
+
import { parseCloudBaseUrl } from './config.mjs'
|
|
3
|
+
|
|
4
|
+
export const CLOUD_SERVICE_SETTINGS_KEY = 'quickforge-cloud-service'
|
|
5
|
+
export const CLOUD_SERVICE_SCHEMA_VERSION = 1
|
|
6
|
+
export const CLOUD_SERVICE_TYPE = 'quickforge-cloud'
|
|
7
|
+
export const DEFAULT_CLOUD_URL = 'http://127.0.0.1:8082/'
|
|
8
|
+
|
|
9
|
+
function savedServiceRecord(settings) {
|
|
10
|
+
const value = settings?.[CLOUD_SERVICE_SETTINGS_KEY]
|
|
11
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function candidateFrom(settings, env) {
|
|
15
|
+
const saved = savedServiceRecord(settings)
|
|
16
|
+
if (saved && typeof saved.cloudUrl === 'string' && saved.cloudUrl.trim()) {
|
|
17
|
+
return { value: saved.cloudUrl, source: 'saved', saved: true }
|
|
18
|
+
}
|
|
19
|
+
if (typeof env?.QUICKFORGE_CLOUD_URL === 'string' && env.QUICKFORGE_CLOUD_URL.trim()) {
|
|
20
|
+
return { value: env.QUICKFORGE_CLOUD_URL, source: 'env', saved: false }
|
|
21
|
+
}
|
|
22
|
+
return { value: DEFAULT_CLOUD_URL, source: 'default', saved: false }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function readCloudServiceConfig({ readSettings = readStore, env = process.env, strict = true } = {}) {
|
|
26
|
+
const settings = await readSettings('settings')
|
|
27
|
+
const candidate = candidateFrom(settings, env)
|
|
28
|
+
try {
|
|
29
|
+
const baseUrl = parseCloudBaseUrl(candidate.value)
|
|
30
|
+
return {
|
|
31
|
+
schemaVersion: CLOUD_SERVICE_SCHEMA_VERSION,
|
|
32
|
+
serviceType: CLOUD_SERVICE_TYPE,
|
|
33
|
+
cloudUrl: baseUrl.href,
|
|
34
|
+
baseUrl,
|
|
35
|
+
source: candidate.source,
|
|
36
|
+
saved: candidate.saved,
|
|
37
|
+
valid: true,
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (strict) throw error
|
|
41
|
+
return {
|
|
42
|
+
schemaVersion: CLOUD_SERVICE_SCHEMA_VERSION,
|
|
43
|
+
serviceType: CLOUD_SERVICE_TYPE,
|
|
44
|
+
cloudUrl: String(candidate.value || '').trim(),
|
|
45
|
+
source: candidate.source,
|
|
46
|
+
saved: candidate.saved,
|
|
47
|
+
valid: false,
|
|
48
|
+
configurationError: error instanceof Error ? error.message : String(error),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function saveCloudServiceConfig(cloudUrl, { updateSettings = atomicUpdate } = {}) {
|
|
54
|
+
const baseUrl = parseCloudBaseUrl(cloudUrl)
|
|
55
|
+
const record = {
|
|
56
|
+
schemaVersion: CLOUD_SERVICE_SCHEMA_VERSION,
|
|
57
|
+
serviceType: CLOUD_SERVICE_TYPE,
|
|
58
|
+
cloudUrl: baseUrl.href,
|
|
59
|
+
}
|
|
60
|
+
await updateSettings('settings', (settings) => ({
|
|
61
|
+
...settings,
|
|
62
|
+
[CLOUD_SERVICE_SETTINGS_KEY]: record,
|
|
63
|
+
}))
|
|
64
|
+
return record
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function publicCloudServiceConfig(config) {
|
|
68
|
+
return {
|
|
69
|
+
schemaVersion: CLOUD_SERVICE_SCHEMA_VERSION,
|
|
70
|
+
serviceType: CLOUD_SERVICE_TYPE,
|
|
71
|
+
cloudUrl: config.cloudUrl,
|
|
72
|
+
source: config.source,
|
|
73
|
+
saved: config.saved,
|
|
74
|
+
valid: config.valid,
|
|
75
|
+
configurationError: config.configurationError,
|
|
76
|
+
}
|
|
77
|
+
}
|