thursday-agent 0.2.0 → 0.3.0
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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +5 -2
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +5 -5
- package/.next/routes-manifest.json +24 -6
- package/.next/server/app/_global-error/page.js +2 -2
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +2 -2
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +2 -2
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +14 -14
- package/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +12 -12
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/artifact/route.js +3 -3
- package/.next/server/app/api/artifact/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/files/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/bot/memory/files/route.js +10 -0
- package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -0
- package/.next/server/app/api/bot/memory/files/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/bot/memory/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/bot/memory/route.js +14 -0
- package/.next/server/app/api/bot/{notes → memory}/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/bot/route.js +6 -6
- package/.next/server/app/api/bot/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/task/[id]/route.js +8 -8
- package/.next/server/app/api/bot/task/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/task/route.js +8 -8
- package/.next/server/app/api/bot/task/route.js.nft.json +1 -1
- package/.next/server/app/api/config/route.js +5 -5
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/events/route.js +4 -5
- package/.next/server/app/api/events/route.js.nft.json +1 -1
- package/.next/server/app/api/file/[...path]/route.js +3 -3
- package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/catalog/route.js +9 -8
- package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/credits/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/llm-model/credits/route/build-manifest.json +12 -0
- package/.next/server/app/api/llm-model/credits/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/llm-model/credits/route.js +16 -0
- package/.next/server/app/api/llm-model/credits/route.js.map +5 -0
- package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -0
- package/.next/server/app/api/llm-model/credits/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/llm-model/route.js +10 -8
- package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/subscription/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/llm-model/subscription/route/build-manifest.json +12 -0
- package/.next/server/app/api/llm-model/subscription/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/llm-model/subscription/route.js +15 -0
- package/.next/server/app/api/llm-model/subscription/route.js.map +5 -0
- package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -0
- package/.next/server/app/api/llm-model/subscription/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/mcp/[name]/route.js +4 -4
- package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/oauth/callback/route.js +5 -5
- package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/route.js +4 -4
- package/.next/server/app/api/mcp/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/tools/route.js +4 -4
- package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/edit/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/memory/edit/route/build-manifest.json +12 -0
- package/.next/server/app/api/memory/edit/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/memory/edit/route.js +24 -0
- package/.next/server/app/api/memory/edit/route.js.map +5 -0
- package/.next/server/app/api/memory/edit/route.js.nft.json +1 -0
- package/.next/server/app/api/memory/{tidy → edit}/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/memory/route.js +6 -7
- package/.next/server/app/api/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/[source]/[name]/route.js +3 -3
- package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js +3 -3
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/call/route.js +9 -7
- package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/skills/route.js +9 -7
- package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/tool-call/route.js +17 -15
- package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/route.js +4 -5
- package/.next/server/app/api/workspace/route.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
- package/.next/server/app/artifact/[...path]/page.js +5 -6
- package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/favicon.ico/route.js +3 -4
- package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
- package/.next/server/app/favicon.ico.body +0 -0
- package/.next/server/app/page/server-reference-manifest.json +57 -90
- package/.next/server/app/page.js +10 -10
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +5 -2
- package/.next/server/chunks/[externals]__0dzuk6_._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__01dkp4a._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__02va5wz._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__02xjoto._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__04q--0f._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__0bl-e63._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0ce8nf8._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__0ffbg85._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0j7z0r3._.js +12 -0
- package/.next/server/chunks/[root-of-the-server]__0jq4ohy._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__0ls6_8y._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__0ncs9uu._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0pa2n68._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0q0vvt2._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__1-ga0kh._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__11n256_._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__12cabg9._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1339jnv._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__15bjnu5._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__18_teo8._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__1_04mcf._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__1anc1p9._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1ccon1w._.js +5 -5
- package/.next/server/chunks/[root-of-the-server]__1fizn31._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1llj3-b._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1orn-5b._.js +11 -0
- package/.next/server/chunks/[root-of-the-server]__1p8m3f4._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1wa1k7r._.js +8 -0
- package/.next/server/chunks/_0-73of8._.js +5 -5
- package/.next/server/chunks/_0-fjql4._.js +26 -0
- package/.next/server/chunks/_00xh_q0._.js +27 -0
- package/.next/server/chunks/{_0pppbb5._.js → _03kp0jp._.js} +163 -36
- package/.next/server/chunks/_051qh8u._.js +3 -0
- package/.next/server/chunks/{node_modules_next_04q-o58._.js → _05dla15._.js} +2 -2
- package/.next/server/chunks/_05h2cii._.js +1 -1
- package/.next/server/chunks/_0c9j6rb._.js +49 -0
- package/.next/server/chunks/_0fzqt2y._.js +130 -0
- package/.next/server/chunks/{_0vnutv9._.js → _0ioz3nd._.js} +1 -1
- package/.next/server/chunks/_0iwfby5._.js +3 -0
- package/.next/server/chunks/_0mecv0f._.js +26 -0
- package/.next/server/chunks/_0mh8j4m._.js +3 -0
- package/.next/server/chunks/_0n74zsf._.js +4 -4
- package/.next/server/chunks/_0obojvv._.js +3 -0
- package/.next/server/chunks/_0recufk._.js +49 -0
- package/.next/server/chunks/_0rnf-6n._.js +4 -4
- package/.next/server/chunks/_0sb7tlz._.js +3 -0
- package/.next/server/chunks/_0u10wmw._.js +5 -5
- package/.next/server/chunks/_0u4aiy2._.js +3 -0
- package/.next/server/chunks/_0wp4le3._.js +1 -1
- package/.next/server/chunks/_0y5fg-x._.js +1 -1
- package/.next/server/chunks/_1230wbx._.js +10 -0
- package/.next/server/chunks/_12nheja._.js +3 -0
- package/.next/server/chunks/_13510xh._.js +3 -0
- package/.next/server/chunks/_15hq89o._.js +1 -1
- package/.next/server/chunks/_17l5ygt._.js +49 -0
- package/.next/server/chunks/_18-1wcc._.js +5 -5
- package/.next/server/chunks/_1_5mnq4._.js +4 -4
- package/.next/server/chunks/_1_9mjx-._.js +3 -0
- package/.next/server/chunks/_1d0j961._.js +6 -0
- package/.next/server/chunks/_1d5zmy8._.js +1 -1
- package/.next/server/chunks/_1drgfx4._.js +4 -4
- package/.next/server/chunks/_1f46eie._.js +3 -0
- package/.next/server/chunks/_1h5_8sk._.js +3 -0
- package/.next/server/chunks/_1hdnxb7._.js +3 -0
- package/.next/server/chunks/_1j8k_j5._.js +3 -0
- package/.next/server/chunks/_1krd9rf._.js +3 -0
- package/.next/server/chunks/_1nc0eui._.js +11 -0
- package/.next/server/chunks/_1nula3v._.js +3 -0
- package/.next/server/chunks/_1pf4qe-._.js +3 -0
- package/.next/server/chunks/_1php4eq._.js +1 -1
- package/.next/server/chunks/_1qxheoa._.js +3 -0
- package/.next/server/chunks/_1x6kd1r._.js +3 -0
- package/.next/server/chunks/_1yuv4vp._.js +1 -1
- package/.next/server/chunks/_1zjrdag._.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_bot_memory_files_route_actions_1_xd_8f.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_bot_memory_route_actions_1a8xfr7.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_llm-model_credits_route_actions_0v424pb.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_llm-model_subscription_route_actions_0-udcm5.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_memory_edit_route_actions_02sfnmr.js +3 -0
- package/.next/server/chunks/config_ts_1g4sz5o._.js +1 -1
- package/.next/server/chunks/features_0rpfxbk._.js +3 -0
- package/.next/server/chunks/features_1qf3cei._.js +3 -0
- package/.next/server/chunks/features_bot_0zt92oh._.js +3 -0
- package/.next/server/chunks/features_bot_bot_query_ts_12mgkl2._.js +3 -0
- package/.next/server/chunks/features_memory_1uqvksn._.js +6 -0
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0a1rd09._.js → node_modules_consola_dist_chunks_prompt_mjs_0i7cvml._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_06067w6._.js → node_modules_consola_dist_chunks_prompt_mjs_0icqaqw._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_096ais_._.js → node_modules_consola_dist_chunks_prompt_mjs_0r28-sm._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_02s3hjt._.js → node_modules_consola_dist_chunks_prompt_mjs_0v_7drq._.js} +1 -1
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1xoq2rl._.js +3 -0
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0tvkfgh._.js → node_modules_consola_dist_chunks_prompt_mjs_1y49572._.js} +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__09q9med._.js +1 -1
- package/.next/server/chunks/ssr/{[root-of-the-server]__1qfqvmg._.js → [root-of-the-server]__0_69wqr._.js} +2 -2
- package/.next/server/chunks/ssr/[root-of-the-server]__0hg5gib._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0i8ie7f._.js +3 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__0jsmmb-._.js → [root-of-the-server]__0mz33a7._.js} +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0tbkdlx._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0vnmv-s._.js +3 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__0f6s48x._.js → [root-of-the-server]__0xpcv9w._.js} +3 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0xtssj3._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0zkv7z_._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__10-dz5o._.js +57 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__15_uhip._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__16jc196._.js +1 -1
- package/.next/server/chunks/ssr/{[root-of-the-server]__0m1b5ky._.js → [root-of-the-server]__1cyjywu._.js} +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1eh7hid._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1gtlw6v._.js +43 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1mzi-ju._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1ox7hbk._.js +4 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1pduf5n._.js +3 -0
- package/.next/server/chunks/ssr/{_1mznrxk._.js → _001_ms1._.js} +3 -3
- package/.next/server/chunks/ssr/_01_6m32._.js +3 -0
- package/.next/server/chunks/ssr/_01n3o0m._.js +3 -0
- package/.next/server/chunks/ssr/_07uchnc._.js +3 -0
- package/.next/server/chunks/ssr/_0ap2fzn._.js +3 -0
- package/.next/server/chunks/ssr/_0c-ql2m._.js +3 -0
- package/.next/server/chunks/ssr/_0h42fze._.js +3 -0
- package/.next/server/chunks/ssr/_0h7zdni._.js +1 -1
- package/.next/server/chunks/ssr/_0i_6q1w._.js +41 -0
- package/.next/server/chunks/ssr/_0kbkmar._.js +3 -0
- package/.next/server/chunks/ssr/{_077-g6s._.js → _0mb8zc8._.js} +3 -3
- package/.next/server/chunks/ssr/_0mo2w06._.js +3 -0
- package/.next/server/chunks/ssr/{_13punpm._.js → _0q0zan6._.js} +2 -2
- package/.next/server/chunks/ssr/{_040sk32._.js → _0u2jv_x._.js} +76 -82
- package/.next/server/chunks/ssr/_0y0tiht._.js +4 -0
- package/.next/server/chunks/ssr/_0zhoc3r._.js +3 -0
- package/.next/server/chunks/ssr/_1-teee5._.js +3 -3
- package/.next/server/chunks/ssr/_13fke0x._.js +3 -0
- package/.next/server/chunks/ssr/{_1lip4th._.js → _13z3f85._.js} +2 -2
- package/.next/server/chunks/ssr/_14_lw10._.js +3 -0
- package/.next/server/chunks/ssr/_15j2_8f._.js +3 -0
- package/.next/server/chunks/ssr/{_09ja_w1._.js → _1eoqg_g._.js} +2 -2
- package/.next/server/chunks/ssr/_1sji22n._.js +3 -0
- package/.next/server/chunks/ssr/_1spzm8z._.js +3 -0
- package/.next/server/chunks/ssr/{_0x_nu5e._.js → _1vi_uo7._.js} +2 -2
- package/.next/server/chunks/ssr/_1vyd1ji._.js +1 -1
- package/.next/server/chunks/ssr/{_1m3bsfr._.js → _1xvgn77._.js} +2 -2
- package/.next/server/chunks/ssr/_20kd_n0._.js +42 -0
- package/.next/server/chunks/ssr/features_bot_1s_-2vj._.js +3 -0
- package/.next/server/chunks/ssr/features_config_components_config-setting_tsx_0xfwqdg._.js +1 -1
- package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
- package/.next/server/chunks/ssr/lib_date-like_ts_02is0pw._.js +3 -0
- package/.next/server/chunks/ssr/{node_modules_18_dk7_._.js → node_modules_063xwpp._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_01qnv-a._.js → node_modules_0bvraul._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0n1kj4p._.js → node_modules_0eu1va6._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0kl7-tn._.js → node_modules_0h50b8n._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0_n241r._.js → node_modules_0h5hnr1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0lrjk5t._.js → node_modules_0h7u60n._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ndvxub._.js → node_modules_0lm7_vm._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_13r44jq._.js → node_modules_0mwym0g._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_17lrt3y._.js → node_modules_0rjg6t1._.js} +1 -1
- package/.next/server/chunks/ssr/node_modules_0x3-ply._.js +3 -0
- package/.next/server/chunks/ssr/{node_modules_004qlr4._.js → node_modules_0y_cded._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0kxz4gt._.js → node_modules_0z5tsu8._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0iz1rzd._.js → node_modules_0zp9r92._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_19dp7ko._.js → node_modules_1-hc6uv._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1q0p85z._.js → node_modules_113tz0l._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1toafc-._.js → node_modules_124bue5._.js} +1 -1
- package/.next/server/chunks/ssr/node_modules_17bwo43._.js +55 -0
- package/.next/server/chunks/ssr/{node_modules_08d8bke._.js → node_modules_1_n3ztw._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1dwguw_._.js → node_modules_1bc7gu8._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_20yebtj._.js → node_modules_1bgb6-f._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1n5ygqe._.js → node_modules_1h4a4pp._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_04n7mjs._.js → node_modules_1i7u1ia._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1hn8z1f._.js → node_modules_1juq3hs._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1xyd7ov._.js → node_modules_1l0qtdu._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0m93zeg._.js → node_modules_1t9cyee._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_09xhur7._.js → node_modules_1uuj9x1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1hd-5tv._.js → node_modules_1vqb6po._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1zu988w._.js → node_modules_1xx28h1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0m9a4y0._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0d3skpl._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_13w_wy9._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0i44kk5._.js} +1 -1
- package/.next/server/chunks/ssr/node_modules_@vercel_oidc_dist_0qe30a_._.js +3 -0
- package/.next/server/chunks/ssr/{node_modules_next_dist_0w6mzq5._.js → node_modules_next_dist_0drixxt._.js} +4 -4
- package/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +2 -2
- package/.next/server/instrumentation.js +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +58 -91
- package/.next/static/chunks/{31ttnjt6g35a0.js → 0ev4hyghq0mbb.js} +1 -1
- package/.next/static/chunks/0r5qcctujhxli.js +1 -0
- package/.next/static/chunks/0tmgrnlje14-s.js +3 -0
- package/.next/static/chunks/0x79tx04dxbpp.js +1 -0
- package/.next/static/chunks/0xzgvc0fkdb6t.js +1 -0
- package/.next/static/chunks/10teww7wt-f6h.js +1 -0
- package/.next/static/chunks/14hgtsjg-kael.css +1 -0
- package/.next/static/chunks/17donp69y4do7.js +40 -0
- package/.next/static/chunks/{3_h9txt3tq8t8.js → 1dkjg87pxk0n9.js} +1 -1
- package/.next/static/chunks/1ekc7ul556kbl.js +1 -0
- package/.next/static/chunks/{3ard_noqapnw1.js → 1grhjo7a5a0_-.js} +1 -1
- package/.next/static/chunks/1kq8h8wi_e0bc.js +53 -0
- package/.next/static/chunks/1okqiww3pnwka.js +1 -0
- package/.next/static/chunks/26-pr2c9ro222.js +1 -0
- package/.next/static/chunks/{18vhba44l1fc9.js → 280t0_e99i276.js} +1 -1
- package/.next/static/chunks/28cizcdaabd4h.js +1 -0
- package/.next/static/chunks/2a0k6jlq0_v1u.js +2 -0
- package/.next/static/chunks/2fzs9fm8yarvq.js +1 -0
- package/.next/static/chunks/2h0sd2vumhb15.js +1 -0
- package/.next/static/chunks/2hw2f6zmt7fmt.js +1 -0
- package/.next/static/chunks/2t4vhi3m8ahya.js +1 -0
- package/.next/static/chunks/2tigdtz3dpcy8.js +1 -0
- package/.next/static/chunks/3111pj5wnjgos.js +1 -0
- package/.next/static/chunks/{07kl352c-lvcz.js → 3agpgkwaa321p.js} +1 -1
- package/.next/static/chunks/3cfi_q6d_wtci.js +1 -0
- package/.next/static/chunks/3ok9_x74rip3v.js +1 -0
- package/.next/static/chunks/3xk7yqgomx7yq.js +1 -0
- package/.next/static/chunks/3xpbkl66ssib5.js +38 -0
- package/.next/static/media/favicon.37o1pjldwi2uc.ico +0 -0
- package/README.md +6 -4
- package/app/api/bot/memory/files/route.ts +14 -0
- package/app/api/bot/memory/route.ts +5 -0
- package/app/api/events/app-event.ts +3 -5
- package/app/api/llm-model/credits/route.ts +11 -0
- package/app/api/llm-model/route.ts +6 -1
- package/app/api/llm-model/subscription/route.ts +11 -0
- package/app/api/memory/edit/route.ts +7 -0
- package/app/api/memory/route.ts +21 -0
- package/app/api/query-key.ts +19 -4
- package/bin/thursday.mjs +3 -0
- package/components/ui/shiny-text.tsx +48 -134
- package/database/migrations/{20260909060813_optimal_sabretooth → 20260910173049_numerous_spirit}/migration.sql +24 -34
- package/database/migrations/{20260909060813_optimal_sabretooth → 20260910173049_numerous_spirit}/snapshot.json +71 -145
- package/database/migrations/20260911030320_opposite_jack_power/migration.sql +1 -0
- package/database/migrations/{20260909063821_bitter_tenebrous → 20260911030320_opposite_jack_power}/snapshot.json +57 -184
- package/features/ai/chatgpt.ts +617 -0
- package/features/ai/components/chatgpt-sign-in.tsx +66 -0
- package/features/ai/components/model-picker.tsx +17 -1
- package/features/ai/components/provider-icon.tsx +1 -0
- package/features/ai/load-tools.ts +58 -42
- package/features/ai/model.schema.ts +232 -34
- package/features/ai/model.ts +169 -24
- package/features/ai/prompts/bot.prompt.ts +156 -65
- package/features/ai/prompts/memory-edit.prompt.ts +33 -0
- package/features/ai/prompts/prompt-helper.ts +45 -26
- package/features/ai/prompts/thursday.prompt.ts +40 -17
- package/features/ai/tools/bot.tool.ts +35 -52
- package/features/ai/tools/connected.ts +17 -4
- package/features/ai/tools/memory.tool.ts +133 -72
- package/features/ai/tools/search.tool.ts +136 -47
- package/features/ai/tools/tool-name.ts +2 -5
- package/features/ai/tools/workspace.tool.ts +2 -1
- package/features/bot/bot.action.ts +8 -18
- package/features/bot/bot.memory.ts +89 -0
- package/features/bot/bot.query.ts +37 -57
- package/features/bot/bot.run.ts +507 -241
- package/features/bot/bot.runner.ts +410 -114
- package/features/bot/bot.schema.ts +79 -19
- package/features/bot/bot.seed.ts +89 -16
- package/features/bot/components/bot-mark.tsx +122 -20
- package/features/bot/components/bot-room.tsx +159 -130
- package/features/bot/components/bot-setting.tsx +790 -402
- package/features/bot/components/bot-tool.tsx +2 -6
- package/features/bot/components/mark-palette.tsx +54 -0
- package/features/bot/components/path-chips.tsx +1 -1
- package/features/bot/components/task-badge.tsx +26 -6
- package/features/bot/components/task-reply.tsx +22 -33
- package/features/bot/components/task-setting.tsx +20 -17
- package/features/bot/task.query.ts +215 -28
- package/features/bot/task.store.ts +53 -9
- package/features/config/components/config-setting.tsx +265 -34
- package/features/config/components/models-badge.tsx +42 -0
- package/features/config/config.action.ts +12 -0
- package/features/config/config.const.ts +38 -2
- package/features/connectors/mcp.manager.ts +10 -2
- package/features/intro/components/intro.tsx +47 -46
- package/features/memory/components/memory-edit.tsx +338 -0
- package/features/memory/components/memory-setting.tsx +75 -77
- package/features/memory/memory.action.ts +3 -43
- package/features/memory/memory.edit.ts +87 -0
- package/features/memory/memory.query.ts +24 -42
- package/features/memory/memory.schema.ts +29 -106
- package/features/settings/components/settings.tsx +2 -0
- package/features/settings/settings.alert.ts +3 -1
- package/features/thursday/components/ascii-orb.tsx +106 -90
- package/features/thursday/components/boot.tsx +7 -0
- package/features/thursday/components/call-log.tsx +219 -92
- package/features/thursday/components/face.tsx +6 -4
- package/features/thursday/components/tab-state.tsx +97 -0
- package/features/thursday/components/thursday-mark.tsx +4 -1
- package/features/thursday/components/thursday-setting.tsx +17 -47
- package/features/thursday/components/thursday.tsx +208 -95
- package/features/thursday/thursday.action.ts +12 -17
- package/features/thursday/thursday.query.ts +61 -70
- package/features/thursday/thursday.schema.ts +20 -7
- package/features/thursday/tool-line.ts +85 -11
- package/features/thursday/use-thursday.ts +86 -59
- package/features/workspace/components/file-view.tsx +4 -2
- package/features/workspace/workspace.ts +110 -26
- package/lib/limits.ts +1 -1
- package/lib/oauth-page.ts +27 -0
- package/lib/realtime/openai.ts +2 -2
- package/lib/realtime/realtime.protocol.ts +1 -1
- package/lib/realtime/realtime.tap.ts +122 -0
- package/lib/sandbox.ts +59 -36
- package/package.json +1 -1
- package/skills/browser/SKILL.md +55 -34
- package/.next/server/app/api/bot/notes/route/app-paths-manifest.json +0 -3
- package/.next/server/app/api/bot/notes/route.js +0 -14
- package/.next/server/app/api/bot/notes/route_client-reference-manifest.js +0 -3
- package/.next/server/app/api/memory/tidy/route/app-paths-manifest.json +0 -3
- package/.next/server/app/api/memory/tidy/route.js +0 -22
- package/.next/server/app/api/memory/tidy/route.js.nft.json +0 -1
- package/.next/server/chunks/0d-b_next_dist_server_app-render_after-task-async-storage_external_0-exn7p.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0-npkh_._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__005var5._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__019dx9f._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__02_2sm-._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__04_8spk._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__05luceg._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__06t77t4._.js +0 -11
- package/.next/server/chunks/[root-of-the-server]__0bed6hu._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__0cro2dz._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0dupqt7._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0emr6go._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0le4d-p._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0m8ywyo._.js +0 -10
- package/.next/server/chunks/[root-of-the-server]__0nfuugi._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0p32jfz._.js +0 -12
- package/.next/server/chunks/[root-of-the-server]__0phdgco._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__0wvgrsl._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__10qshki._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__11j52t0._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__1692zrv._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__16thabu._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__173nhwf._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__1_aoyr9._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1bn73ih._.js +0 -10
- package/.next/server/chunks/[root-of-the-server]__1bqhuh9._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__1cqizfq._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__1e-ci13._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1fboj2j._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1lr7j9t._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__1oli73c._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1txedx6._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__1untqv8._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__1zgpz1w._.js +0 -3
- package/.next/server/chunks/_00_584r._.js +0 -293
- package/.next/server/chunks/_012yhfm._.js +0 -39
- package/.next/server/chunks/_06mxlgn._.js +0 -3
- package/.next/server/chunks/_07rtlxv._.js +0 -3
- package/.next/server/chunks/_0a351g7._.js +0 -6
- package/.next/server/chunks/_0kjjmag._.js +0 -3
- package/.next/server/chunks/_0nn99o2._.js +0 -3
- package/.next/server/chunks/_0zuzmt_._.js +0 -39
- package/.next/server/chunks/_106co2o._.js +0 -3
- package/.next/server/chunks/_12bbv5i._.js +0 -100
- package/.next/server/chunks/_15q1pig._.js +0 -3
- package/.next/server/chunks/_19wg5yn._.js +0 -138
- package/.next/server/chunks/_1bt95p9._.js +0 -55
- package/.next/server/chunks/_1cbk7ms._.js +0 -26
- package/.next/server/chunks/_1drv2et._.js +0 -40
- package/.next/server/chunks/_1ic3bew._.js +0 -5
- package/.next/server/chunks/_1mxn_lb._.js +0 -3
- package/.next/server/chunks/_1rysr-r._.js +0 -10
- package/.next/server/chunks/_1u3rwbr._.js +0 -3
- package/.next/server/chunks/_1x7p319._.js +0 -6
- package/.next/server/chunks/_next-internal_server_app_api_bot_notes_route_actions_0qcsyu7.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_memory_tidy_route_actions_0_cfuom.js +0 -3
- package/.next/server/chunks/features_bot_task_query_ts_11xuzl3._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0gzvgfv._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0vorg6w._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1tpvamf._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1vmaq9f._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1y9-wcr._.js +0 -3
- package/.next/server/chunks/ssr/[externals]_node_crypto_1kn79-7._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0iuoa-r._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0un-erv._.js +0 -4
- package/.next/server/chunks/ssr/[root-of-the-server]__1-mnhyd._.js +0 -92
- package/.next/server/chunks/ssr/[root-of-the-server]__1bww500._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1vcdast._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1xwnu-h._.js +0 -3
- package/.next/server/chunks/ssr/_0-mc7ro._.js +0 -3
- package/.next/server/chunks/ssr/_01p5r6-._.js +0 -3
- package/.next/server/chunks/ssr/_03webdr._.js +0 -3
- package/.next/server/chunks/ssr/_09-0k3z._.js +0 -3
- package/.next/server/chunks/ssr/_0f_b_9s._.js +0 -3
- package/.next/server/chunks/ssr/_0fafgvl._.js +0 -3
- package/.next/server/chunks/ssr/_0sf1isi._.js +0 -3
- package/.next/server/chunks/ssr/_0su6_08._.js +0 -3
- package/.next/server/chunks/ssr/_0ti51xc._.js +0 -3
- package/.next/server/chunks/ssr/_0w5bgak._.js +0 -3
- package/.next/server/chunks/ssr/_0y84qo6._.js +0 -3
- package/.next/server/chunks/ssr/_1ciyoo3._.js +0 -3
- package/.next/server/chunks/ssr/_1e-dgz0._.js +0 -4
- package/.next/server/chunks/ssr/_1fq7zgb._.js +0 -3
- package/.next/server/chunks/ssr/_1g6l4uj._.js +0 -3
- package/.next/server/chunks/ssr/_1ruzehw._.js +0 -3
- package/.next/server/chunks/ssr/features_bot_components_bot-setting_tsx_01ajft6._.js +0 -4
- package/.next/server/chunks/ssr/features_config_components_14jqgy4._.js +0 -3
- package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +0 -4
- package/.next/server/chunks/ssr/node_modules_zod_v4_classic_external_15fmmz3.js +0 -55
- package/.next/static/chunks/05b6nx_o3_f_n.js +0 -1
- package/.next/static/chunks/0dmns-gi0raa4.js +0 -1
- package/.next/static/chunks/0f9sq-7q_yxyl.js +0 -1
- package/.next/static/chunks/0oc4_xoia77dg.css +0 -1
- package/.next/static/chunks/0pheo8m3a-rzi.js +0 -1
- package/.next/static/chunks/19i8h2t-5evjx.js +0 -1
- package/.next/static/chunks/1c5fcj473025q.js +0 -1
- package/.next/static/chunks/1pi_hbmjw-f-z.js +0 -1
- package/.next/static/chunks/1twt6k09vg4np.js +0 -1
- package/.next/static/chunks/1ytnip8wdng93.js +0 -1
- package/.next/static/chunks/28gotuisldmom.js +0 -1
- package/.next/static/chunks/2998p0y7k1c8z.js +0 -3
- package/.next/static/chunks/29gyb3nubj51j.js +0 -53
- package/.next/static/chunks/2l233xfycjpd8.js +0 -1
- package/.next/static/chunks/2p5n7pa3c40t-.js +0 -2
- package/.next/static/chunks/2ru1q132af7h7.js +0 -1
- package/.next/static/chunks/2t8wqycwg3y04.js +0 -2
- package/.next/static/chunks/2uxum7qbyeyxd.js +0 -1
- package/.next/static/chunks/32z_ypgtchw48.js +0 -1
- package/.next/static/chunks/33pyjb3xd1iez.js +0 -1
- package/.next/static/chunks/3omud5xunrk1f.js +0 -1
- package/.next/static/chunks/42dnrkbph9e-1.js +0 -1
- package/.next/static/media/favicon.2vob68tjqpejf.ico +0 -0
- package/database/migrations/20260909063821_bitter_tenebrous/migration.sql +0 -1
- package/features/ai/prompts/memory-tidy.prompt.ts +0 -129
- package/features/ai/tools/tidy.tool.ts +0 -22
- package/features/bot/bot.notes.ts +0 -120
- package/features/memory/components/memory-tidy.tsx +0 -395
- package/features/memory/memory.tidy.ts +0 -332
- package/features/memory/tidy.query.ts +0 -135
- package/features/thursday/use-call-title.ts +0 -31
- /package/.next/server/app/api/bot/{notes → memory/files}/route/build-manifest.json +0 -0
- /package/.next/server/app/api/bot/{notes → memory/files}/route/server-reference-manifest.json +0 -0
- /package/.next/server/app/api/bot/{notes → memory/files}/route.js.map +0 -0
- /package/.next/server/app/api/{memory/tidy → bot/memory}/route/build-manifest.json +0 -0
- /package/.next/server/app/api/{memory/tidy → bot/memory}/route/server-reference-manifest.json +0 -0
- /package/.next/server/app/api/{memory/tidy → bot/memory}/route.js.map +0 -0
- /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → x1cCqY0kWpR_VcYx_lWO5}/_buildManifest.js +0 -0
- /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → x1cCqY0kWpR_VcYx_lWO5}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → x1cCqY0kWpR_VcYx_lWO5}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
2
|
+
import { createServer, type Server } from "node:http";
|
|
3
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
4
|
+
import { type LanguageModel, wrapLanguageModel } from "ai";
|
|
5
|
+
import { formatDistanceToNowStrict } from "date-fns";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { appEvents } from "@/app/api/events/app-event.server";
|
|
8
|
+
import { CHATGPT_SIGN_IN, CHATGPT_USAGE_HIGH } from "@/config";
|
|
9
|
+
import { readConfig, writeConfig } from "@/features/config/config.query";
|
|
10
|
+
import { logger } from "@/lib/logger";
|
|
11
|
+
import { oauthPage } from "@/lib/oauth-page";
|
|
12
|
+
import { publicError } from "@/lib/public-error";
|
|
13
|
+
import { createKeyedLock } from "@/lib/queue";
|
|
14
|
+
import { errorToString } from "@/lib/utils";
|
|
15
|
+
import { type SubscriptionUsage, TEXT_MODEL_PROVIDERS } from "./model.schema";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* ChatGPT as a text provider: the Codex usage a ChatGPT plan carries, in place of an API key.
|
|
19
|
+
* Signing in is OpenAI's OAuth for the Codex CLI, the way other agent apps reach a plan. The
|
|
20
|
+
* model is the Responses API behind chatgpt.com, which takes less than api.openai.com does;
|
|
21
|
+
* `codexFetch` is the difference.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** The Codex CLI's public OAuth client. */
|
|
25
|
+
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
26
|
+
const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
|
|
27
|
+
const TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
28
|
+
/** The only redirect registered for that client, so the answer comes back to this port and no other. */
|
|
29
|
+
const CALLBACK_PORT = 1455;
|
|
30
|
+
const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}/auth/callback`;
|
|
31
|
+
const SCOPE = "openid profile email offline_access";
|
|
32
|
+
const CODEX_URL = "https://chatgpt.com/backend-api/codex";
|
|
33
|
+
/** The access token's claim naming the account and its plan. */
|
|
34
|
+
const AUTH_CLAIM = "https://api.openai.com/auth";
|
|
35
|
+
/** How this app names itself to OpenAI. */
|
|
36
|
+
const ORIGINATOR = "thursday";
|
|
37
|
+
|
|
38
|
+
const { apiKeyName: SIGN_IN_KEY, label: LABEL } = TEXT_MODEL_PROVIDERS.chatgpt;
|
|
39
|
+
const NOT_SIGNED_IN = `${LABEL} is not signed in — sign in from Config.`;
|
|
40
|
+
|
|
41
|
+
/** What a sign-in leaves in config. Tokens: it never reaches the browser. */
|
|
42
|
+
const SignInSchema = z.object({
|
|
43
|
+
access: z.string().min(1),
|
|
44
|
+
refresh: z.string().min(1),
|
|
45
|
+
/** When the access token runs out, epoch ms. */
|
|
46
|
+
expires: z.number(),
|
|
47
|
+
accountId: z.string().min(1),
|
|
48
|
+
/** The plan as the token names it ("free", "plus", "pro"); null when it does not say. */
|
|
49
|
+
plan: z.string().nullable(),
|
|
50
|
+
});
|
|
51
|
+
type SignIn = z.infer<typeof SignInSchema>;
|
|
52
|
+
|
|
53
|
+
type Pinned = {
|
|
54
|
+
__chatgptListener?: Server | null;
|
|
55
|
+
__chatgptRenewal?: ReturnType<typeof createKeyedLock>;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** Pinned to globalThis: next dev reloads this module, and the listener holding the port and the renewal lane must be the originals. */
|
|
59
|
+
const pinned = globalThis as Pinned;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* One renewal at a time. A refresh token is spent by using it, so two requests renewing at
|
|
63
|
+
* once leave one holding a token OpenAI has already retired — which signs the app out.
|
|
64
|
+
*/
|
|
65
|
+
const renewal = (pinned.__chatgptRenewal ??= createKeyedLock());
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Starts a sign-in and returns the page to send the browser to. The answer comes back to the
|
|
69
|
+
* loopback port, so the app listens there until it arrives or `CHATGPT_SIGN_IN.waitMs` passes;
|
|
70
|
+
* starting again replaces a sign-in still waiting.
|
|
71
|
+
*/
|
|
72
|
+
export async function startChatGptSignIn(): Promise<string> {
|
|
73
|
+
stopListening();
|
|
74
|
+
const verifier = randomBytes(32).toString("base64url");
|
|
75
|
+
const state = randomBytes(16).toString("hex");
|
|
76
|
+
|
|
77
|
+
const server = createServer((request, response) => {
|
|
78
|
+
void answerCallback(request.url ?? "/", { state, verifier }).then(
|
|
79
|
+
(page) => {
|
|
80
|
+
response.writeHead(page.status, {
|
|
81
|
+
"content-type": "text/html; charset=utf-8",
|
|
82
|
+
});
|
|
83
|
+
response.end(
|
|
84
|
+
oauthPage(page.title, page.detail, {
|
|
85
|
+
autoClose: page.status === 200,
|
|
86
|
+
}),
|
|
87
|
+
() => {
|
|
88
|
+
if (page.last && pinned.__chatgptListener === server) {
|
|
89
|
+
stopListening();
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await new Promise<void>((resolve, reject) => {
|
|
98
|
+
server.once("error", reject);
|
|
99
|
+
server.listen(CALLBACK_PORT, "127.0.0.1", () => resolve());
|
|
100
|
+
}).catch((cause: NodeJS.ErrnoException) => {
|
|
101
|
+
publicError(
|
|
102
|
+
cause.code === "EADDRINUSE"
|
|
103
|
+
? `Port ${CALLBACK_PORT} is in use — another ChatGPT or Codex sign-in is waiting. Finish or close it, then try again.`
|
|
104
|
+
: `Could not wait for the sign-in: ${errorToString(cause)}`,
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
pinned.__chatgptListener = server;
|
|
108
|
+
// A page nobody finishes must not hold the port for the life of the process
|
|
109
|
+
setTimeout(() => {
|
|
110
|
+
if (pinned.__chatgptListener === server) stopListening();
|
|
111
|
+
}, CHATGPT_SIGN_IN.waitMs).unref();
|
|
112
|
+
|
|
113
|
+
const url = new URL(AUTHORIZE_URL);
|
|
114
|
+
url.search = new URLSearchParams({
|
|
115
|
+
response_type: "code",
|
|
116
|
+
client_id: CLIENT_ID,
|
|
117
|
+
redirect_uri: REDIRECT_URI,
|
|
118
|
+
scope: SCOPE,
|
|
119
|
+
code_challenge: createHash("sha256").update(verifier).digest("base64url"),
|
|
120
|
+
code_challenge_method: "S256",
|
|
121
|
+
state,
|
|
122
|
+
// What the Codex CLI asks for: the account's organizations in the token, and the short consent page
|
|
123
|
+
id_token_add_organizations: "true",
|
|
124
|
+
codex_cli_simplified_flow: "true",
|
|
125
|
+
originator: ORIGINATOR,
|
|
126
|
+
}).toString();
|
|
127
|
+
return url.toString();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function stopListening() {
|
|
131
|
+
const server = pinned.__chatgptListener;
|
|
132
|
+
pinned.__chatgptListener = null;
|
|
133
|
+
server?.close();
|
|
134
|
+
server?.closeIdleConnections();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
type CallbackPage = {
|
|
138
|
+
status: number;
|
|
139
|
+
title: string;
|
|
140
|
+
detail: string;
|
|
141
|
+
/** The sign-in is over, however it went, so the port is let go. */
|
|
142
|
+
last: boolean;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
async function answerCallback(
|
|
146
|
+
path: string,
|
|
147
|
+
flow: { state: string; verifier: string },
|
|
148
|
+
): Promise<CallbackPage> {
|
|
149
|
+
const url = new URL(path, REDIRECT_URI);
|
|
150
|
+
// A browser asks the same port for a favicon; that is not the answer
|
|
151
|
+
if (url.pathname !== "/auth/callback") {
|
|
152
|
+
return { status: 404, title: "Not found", detail: "", last: false };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const error = url.searchParams.get("error");
|
|
156
|
+
if (error) {
|
|
157
|
+
const description = url.searchParams.get("error_description");
|
|
158
|
+
return {
|
|
159
|
+
status: 400,
|
|
160
|
+
title: "Sign-in failed",
|
|
161
|
+
detail: description ? `${error}: ${description}` : error,
|
|
162
|
+
last: true,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const code = url.searchParams.get("code");
|
|
166
|
+
if (!code || url.searchParams.get("state") !== flow.state) {
|
|
167
|
+
return {
|
|
168
|
+
status: 400,
|
|
169
|
+
title: "Sign-in failed",
|
|
170
|
+
detail:
|
|
171
|
+
"This is not the sign-in that was started. Start again from Config.",
|
|
172
|
+
last: false,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const signIn = await requestToken({
|
|
178
|
+
grant_type: "authorization_code",
|
|
179
|
+
code,
|
|
180
|
+
code_verifier: flow.verifier,
|
|
181
|
+
redirect_uri: REDIRECT_URI,
|
|
182
|
+
});
|
|
183
|
+
await writeConfig(SIGN_IN_KEY, JSON.stringify(signIn));
|
|
184
|
+
// The screen that opened this page is waiting on it
|
|
185
|
+
appEvents.emit({ type: "config" });
|
|
186
|
+
return {
|
|
187
|
+
status: 200,
|
|
188
|
+
title: "Signed in to ChatGPT",
|
|
189
|
+
detail: "Bots can run on your plan now. This window closes itself.",
|
|
190
|
+
last: true,
|
|
191
|
+
};
|
|
192
|
+
} catch (cause) {
|
|
193
|
+
logger.warn({ cause }, "chatgpt sign-in failed");
|
|
194
|
+
return {
|
|
195
|
+
status: 502,
|
|
196
|
+
title: "Sign-in failed",
|
|
197
|
+
detail: errorToString(cause),
|
|
198
|
+
last: true,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** The account, plan and expiry an access token carries. Read, never verified: it came from OpenAI over TLS. */
|
|
204
|
+
function claimsOf(access: string) {
|
|
205
|
+
try {
|
|
206
|
+
const payload = JSON.parse(
|
|
207
|
+
Buffer.from(access.split(".")[1] ?? "", "base64url").toString("utf8"),
|
|
208
|
+
) as Record<string, unknown>;
|
|
209
|
+
const auth = (payload[AUTH_CLAIM] ?? {}) as Record<string, unknown>;
|
|
210
|
+
return {
|
|
211
|
+
accountId:
|
|
212
|
+
typeof auth.chatgpt_account_id === "string"
|
|
213
|
+
? auth.chatgpt_account_id
|
|
214
|
+
: null,
|
|
215
|
+
plan:
|
|
216
|
+
typeof auth.chatgpt_plan_type === "string"
|
|
217
|
+
? auth.chatgpt_plan_type
|
|
218
|
+
: null,
|
|
219
|
+
expires: typeof payload.exp === "number" ? payload.exp * 1000 : null,
|
|
220
|
+
};
|
|
221
|
+
} catch {
|
|
222
|
+
return { accountId: null, plan: null, expires: null };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** One call to OpenAI's token endpoint — the first exchange, or a renewal. A refusal comes back in its own words. */
|
|
227
|
+
async function requestToken(fields: Record<string, string>): Promise<SignIn> {
|
|
228
|
+
const response = await fetch(TOKEN_URL, {
|
|
229
|
+
method: "POST",
|
|
230
|
+
headers: {
|
|
231
|
+
accept: "application/json",
|
|
232
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
233
|
+
},
|
|
234
|
+
body: new URLSearchParams({ client_id: CLIENT_ID, ...fields }),
|
|
235
|
+
}).catch((cause: unknown) => {
|
|
236
|
+
logger.warn({ cause }, "chatgpt token endpoint unreachable");
|
|
237
|
+
publicError("Could not reach ChatGPT's sign-in");
|
|
238
|
+
});
|
|
239
|
+
const body = (await response.json().catch(() => null)) as {
|
|
240
|
+
access_token?: string;
|
|
241
|
+
refresh_token?: string;
|
|
242
|
+
expires_in?: number;
|
|
243
|
+
error?: string | { message?: string; code?: string };
|
|
244
|
+
error_description?: string;
|
|
245
|
+
} | null;
|
|
246
|
+
|
|
247
|
+
const access = body?.access_token;
|
|
248
|
+
if (!response.ok || !access) {
|
|
249
|
+
const error = body?.error;
|
|
250
|
+
const said =
|
|
251
|
+
typeof error === "string"
|
|
252
|
+
? [error, body?.error_description].filter(Boolean).join(": ")
|
|
253
|
+
: (error?.message ?? error?.code ?? "");
|
|
254
|
+
publicError(
|
|
255
|
+
`ChatGPT's sign-in answered ${response.status}${said ? `: ${said}` : ""}`,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const claims = claimsOf(access);
|
|
260
|
+
const refresh = body?.refresh_token ?? fields.refresh_token;
|
|
261
|
+
if (!claims.accountId || !refresh) {
|
|
262
|
+
publicError("ChatGPT's sign-in came back without an account");
|
|
263
|
+
}
|
|
264
|
+
const expiresIn = body?.expires_in;
|
|
265
|
+
return {
|
|
266
|
+
access,
|
|
267
|
+
refresh,
|
|
268
|
+
expires:
|
|
269
|
+
typeof expiresIn === "number"
|
|
270
|
+
? Date.now() + expiresIn * 1000
|
|
271
|
+
: (claims.expires ?? Date.now()),
|
|
272
|
+
accountId: claims.accountId,
|
|
273
|
+
plan: claims.plan,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function readSignIn(): Promise<SignIn | null> {
|
|
278
|
+
const stored = await readConfig(SIGN_IN_KEY);
|
|
279
|
+
if (!stored) return null;
|
|
280
|
+
try {
|
|
281
|
+
return SignInSchema.parse(JSON.parse(stored));
|
|
282
|
+
} catch {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const expiring = (signIn: SignIn) =>
|
|
288
|
+
signIn.expires - CHATGPT_SIGN_IN.renewBeforeMs <= Date.now();
|
|
289
|
+
|
|
290
|
+
/** The sign-in to send a request with, renewed first when its access token is about to run out. */
|
|
291
|
+
async function currentSignIn(): Promise<SignIn> {
|
|
292
|
+
const stored = await readSignIn();
|
|
293
|
+
if (!stored) publicError(NOT_SIGNED_IN);
|
|
294
|
+
if (!expiring(stored)) return stored;
|
|
295
|
+
|
|
296
|
+
return renewal(SIGN_IN_KEY, async () => {
|
|
297
|
+
// Another request may have renewed it while this one waited its turn
|
|
298
|
+
const latest = await readSignIn();
|
|
299
|
+
if (!latest) publicError(NOT_SIGNED_IN);
|
|
300
|
+
if (!expiring(latest)) return latest;
|
|
301
|
+
try {
|
|
302
|
+
const renewed = await requestToken({
|
|
303
|
+
grant_type: "refresh_token",
|
|
304
|
+
refresh_token: latest.refresh,
|
|
305
|
+
});
|
|
306
|
+
await writeConfig(SIGN_IN_KEY, JSON.stringify(renewed));
|
|
307
|
+
return renewed;
|
|
308
|
+
} catch (cause) {
|
|
309
|
+
publicError(
|
|
310
|
+
`Could not renew the ${LABEL} sign-in (${errorToString(cause)}). If it keeps failing, sign in again from Config.`,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** The plan the signed-in account is on, for the settings screen; null when nobody is signed in. */
|
|
317
|
+
export async function readChatGptPlan(): Promise<string | null> {
|
|
318
|
+
return (await readSignIn())?.plan ?? null;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Answers the sign-in's own token, so no second key is needed to say what is left. */
|
|
322
|
+
const USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
323
|
+
|
|
324
|
+
type UsageWindow = {
|
|
325
|
+
used_percent?: number;
|
|
326
|
+
/** Epoch seconds. */
|
|
327
|
+
reset_at?: number;
|
|
328
|
+
reset_after_seconds?: number;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* How much of the plan the signed-in account has used, by its tightest window — Free has one of
|
|
333
|
+
* 30 days, a paid plan a short one and a weekly one — and when that window frees up; null when
|
|
334
|
+
* nobody is signed in. A sign-in the plan turns away is a state of the sign-in, not a failed
|
|
335
|
+
* read, so it comes back as `refused` in OpenAI's words.
|
|
336
|
+
*/
|
|
337
|
+
export async function readChatGptUsage(): Promise<SubscriptionUsage | null> {
|
|
338
|
+
if (!(await readSignIn())) return null;
|
|
339
|
+
let signIn: SignIn;
|
|
340
|
+
try {
|
|
341
|
+
signIn = await currentSignIn();
|
|
342
|
+
} catch (cause) {
|
|
343
|
+
return { refused: errorToString(cause) };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const response = await fetch(USAGE_URL, {
|
|
347
|
+
headers: {
|
|
348
|
+
accept: "application/json",
|
|
349
|
+
authorization: `Bearer ${signIn.access}`,
|
|
350
|
+
"chatgpt-account-id": signIn.accountId,
|
|
351
|
+
originator: ORIGINATOR,
|
|
352
|
+
},
|
|
353
|
+
}).catch((cause: unknown) => {
|
|
354
|
+
logger.warn({ cause }, "chatgpt usage unreachable");
|
|
355
|
+
publicError("Could not reach ChatGPT");
|
|
356
|
+
});
|
|
357
|
+
const body = (await response.json().catch(() => null)) as {
|
|
358
|
+
plan_type?: string;
|
|
359
|
+
rate_limit?: {
|
|
360
|
+
limit_reached?: boolean;
|
|
361
|
+
primary_window?: UsageWindow | null;
|
|
362
|
+
secondary_window?: UsageWindow | null;
|
|
363
|
+
};
|
|
364
|
+
detail?: string;
|
|
365
|
+
error?: { message?: string };
|
|
366
|
+
} | null;
|
|
367
|
+
|
|
368
|
+
if (response.status === 401 || response.status === 403) {
|
|
369
|
+
return {
|
|
370
|
+
refused:
|
|
371
|
+
body?.error?.message ??
|
|
372
|
+
body?.detail ??
|
|
373
|
+
`ChatGPT answered ${response.status}`,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
if (!response.ok) publicError(`ChatGPT answered ${response.status}`);
|
|
377
|
+
|
|
378
|
+
const tightest = [
|
|
379
|
+
body?.rate_limit?.primary_window,
|
|
380
|
+
body?.rate_limit?.secondary_window,
|
|
381
|
+
]
|
|
382
|
+
.filter(
|
|
383
|
+
(window): window is UsageWindow =>
|
|
384
|
+
typeof window?.used_percent === "number",
|
|
385
|
+
)
|
|
386
|
+
.sort((a, b) => (b.used_percent ?? 0) - (a.used_percent ?? 0))[0];
|
|
387
|
+
if (!tightest)
|
|
388
|
+
publicError("ChatGPT did not say how much of the plan is used");
|
|
389
|
+
|
|
390
|
+
const usedPercent = Math.round(tightest.used_percent ?? 0);
|
|
391
|
+
const spent = Boolean(body?.rate_limit?.limit_reached);
|
|
392
|
+
const resetsAt = tightest.reset_at
|
|
393
|
+
? tightest.reset_at * 1000
|
|
394
|
+
: tightest.reset_after_seconds
|
|
395
|
+
? Date.now() + tightest.reset_after_seconds * 1000
|
|
396
|
+
: null;
|
|
397
|
+
return {
|
|
398
|
+
plan: body?.plan_type ?? signIn.plan,
|
|
399
|
+
usedPercent,
|
|
400
|
+
resetsAt: resetsAt ? new Date(resetsAt).toISOString() : null,
|
|
401
|
+
spent,
|
|
402
|
+
high: spent || usedPercent >= CHATGPT_USAGE_HIGH,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** A Codex model on the signed-in plan. */
|
|
407
|
+
export function chatGptModel(modelId: string): LanguageModel {
|
|
408
|
+
const chatgpt = createOpenAI({
|
|
409
|
+
baseURL: CODEX_URL,
|
|
410
|
+
// The sdk builds no request without a key; codexFetch signs every one with the sign-in instead
|
|
411
|
+
apiKey: "signed-in",
|
|
412
|
+
headers: { "OpenAI-Beta": "responses=experimental" },
|
|
413
|
+
fetch: codexFetch,
|
|
414
|
+
});
|
|
415
|
+
// One key for the model's life — a run, its compactions, its answers — so its calls share a prompt cache
|
|
416
|
+
const promptCacheKey = randomUUID();
|
|
417
|
+
return wrapLanguageModel({
|
|
418
|
+
model: chatgpt.responses(modelId),
|
|
419
|
+
middleware: {
|
|
420
|
+
// Unstored: the sdk then sends whole items rather than ids the backend never kept, and asks
|
|
421
|
+
// for the encrypted reasoning a stored thread needs to be replayed
|
|
422
|
+
transformParams: async ({ params }) => ({
|
|
423
|
+
...params,
|
|
424
|
+
providerOptions: {
|
|
425
|
+
...params.providerOptions,
|
|
426
|
+
openai: {
|
|
427
|
+
promptCacheKey,
|
|
428
|
+
...params.providerOptions?.openai,
|
|
429
|
+
store: false,
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
}),
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
type CodexBody = {
|
|
438
|
+
input?: { role?: string; content?: string | { text?: string }[] }[];
|
|
439
|
+
instructions?: string;
|
|
440
|
+
stream?: boolean;
|
|
441
|
+
store?: boolean;
|
|
442
|
+
max_output_tokens?: number;
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* What the ChatGPT backend asks of a Responses call that api.openai.com does not, applied to
|
|
447
|
+
* every request the sdk makes: signed with the current sign-in, so a job that outlives a token
|
|
448
|
+
* renews it; always streamed and never stored; the system prompt as `instructions`, which it
|
|
449
|
+
* refuses a call without; no output cap.
|
|
450
|
+
*/
|
|
451
|
+
async function codexFetch(
|
|
452
|
+
input: RequestInfo | URL,
|
|
453
|
+
init?: RequestInit,
|
|
454
|
+
): Promise<Response> {
|
|
455
|
+
const signIn = await currentSignIn();
|
|
456
|
+
const headers = new Headers(init?.headers);
|
|
457
|
+
headers.set("authorization", `Bearer ${signIn.access}`);
|
|
458
|
+
headers.set("chatgpt-account-id", signIn.accountId);
|
|
459
|
+
headers.set("originator", ORIGINATOR);
|
|
460
|
+
|
|
461
|
+
if (
|
|
462
|
+
init?.method !== "POST" ||
|
|
463
|
+
typeof init.body !== "string" ||
|
|
464
|
+
!String(input).endsWith("/responses")
|
|
465
|
+
) {
|
|
466
|
+
return fetch(input, { ...init, headers });
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const body = JSON.parse(init.body) as CodexBody;
|
|
470
|
+
const streamed = body.stream === true;
|
|
471
|
+
const system: string[] = [];
|
|
472
|
+
body.input = (body.input ?? []).filter((item) => {
|
|
473
|
+
if (item.role !== "system" && item.role !== "developer") return true;
|
|
474
|
+
system.push(
|
|
475
|
+
typeof item.content === "string"
|
|
476
|
+
? item.content
|
|
477
|
+
: (item.content ?? []).map((part) => part.text ?? "").join("\n"),
|
|
478
|
+
);
|
|
479
|
+
return false;
|
|
480
|
+
});
|
|
481
|
+
body.instructions =
|
|
482
|
+
[body.instructions, ...system].filter(Boolean).join("\n\n") ||
|
|
483
|
+
"You are a helpful assistant.";
|
|
484
|
+
body.store = false;
|
|
485
|
+
body.stream = true;
|
|
486
|
+
body.max_output_tokens = undefined;
|
|
487
|
+
|
|
488
|
+
headers.set("accept", "text/event-stream");
|
|
489
|
+
const response = await fetch(input, {
|
|
490
|
+
...init,
|
|
491
|
+
headers,
|
|
492
|
+
body: JSON.stringify(body),
|
|
493
|
+
});
|
|
494
|
+
if (response.status === 429) return usageLimitOf(response);
|
|
495
|
+
if (streamed || !response.ok) return response;
|
|
496
|
+
return finishedOf(response);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* A 429 is either a rate limit a moment fixes or the plan's usage spent until its window resets
|
|
501
|
+
* — hours, or on Free a month. The second becomes a 402 in the backend's words: the sdk does not
|
|
502
|
+
* retry it, and a job fails with it (model.ts modelFailureOf) instead of waking every few
|
|
503
|
+
* minutes to be refused again.
|
|
504
|
+
*/
|
|
505
|
+
async function usageLimitOf(response: Response): Promise<Response> {
|
|
506
|
+
const text = await response.text();
|
|
507
|
+
let said: {
|
|
508
|
+
code?: string;
|
|
509
|
+
type?: string;
|
|
510
|
+
plan_type?: string;
|
|
511
|
+
resets_at?: number;
|
|
512
|
+
resets_in_seconds?: number;
|
|
513
|
+
} = {};
|
|
514
|
+
try {
|
|
515
|
+
said = (JSON.parse(text) as { error?: typeof said }).error ?? {};
|
|
516
|
+
} catch {}
|
|
517
|
+
|
|
518
|
+
if (
|
|
519
|
+
!/usage_limit_reached|usage_not_included/.test(`${said.code} ${said.type}`)
|
|
520
|
+
) {
|
|
521
|
+
const retryAfter = response.headers.get("retry-after");
|
|
522
|
+
return new Response(text, {
|
|
523
|
+
status: 429,
|
|
524
|
+
headers: {
|
|
525
|
+
"content-type": "application/json",
|
|
526
|
+
...(retryAfter ? { "retry-after": retryAfter } : {}),
|
|
527
|
+
},
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const resets = said.resets_at
|
|
532
|
+
? said.resets_at * 1000
|
|
533
|
+
: said.resets_in_seconds
|
|
534
|
+
? Date.now() + said.resets_in_seconds * 1000
|
|
535
|
+
: null;
|
|
536
|
+
const plan = said.plan_type ? ` on the ${said.plan_type} plan` : "";
|
|
537
|
+
const when = resets
|
|
538
|
+
? ` It resets in ${formatDistanceToNowStrict(resets)}.`
|
|
539
|
+
: "";
|
|
540
|
+
return Response.json(
|
|
541
|
+
{
|
|
542
|
+
error: {
|
|
543
|
+
message: `${LABEL} usage limit reached${plan}.${when}`,
|
|
544
|
+
code: said.code ?? said.type,
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
{ status: 402 },
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
type StreamEvent = {
|
|
552
|
+
type?: string;
|
|
553
|
+
item?: unknown;
|
|
554
|
+
response?: {
|
|
555
|
+
output?: unknown[];
|
|
556
|
+
error?: { code?: string; message?: string } | null;
|
|
557
|
+
};
|
|
558
|
+
code?: string;
|
|
559
|
+
message?: string;
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* The one JSON body a caller that did not stream expects, read off the stream the backend
|
|
564
|
+
* insists on. The finished event's `output` arrives empty, so items are gathered as each
|
|
565
|
+
* completes. A failed response becomes an error in its own words: a 400 when it was the
|
|
566
|
+
* context, which a compaction fixes, a 500 otherwise.
|
|
567
|
+
*/
|
|
568
|
+
async function finishedOf(stream: Response): Promise<Response> {
|
|
569
|
+
const items: unknown[] = [];
|
|
570
|
+
let finished: { output?: unknown[] } | null = null;
|
|
571
|
+
let failure: { code?: string; message?: string } | null = null;
|
|
572
|
+
|
|
573
|
+
const blocks = (await stream.text()).replaceAll("\r\n", "\n").split("\n\n");
|
|
574
|
+
for (const block of blocks) {
|
|
575
|
+
const data = block
|
|
576
|
+
.split("\n")
|
|
577
|
+
.filter((line) => line.startsWith("data:"))
|
|
578
|
+
.map((line) => line.slice(5).trimStart())
|
|
579
|
+
.join("\n");
|
|
580
|
+
if (!data || data === "[DONE]") continue;
|
|
581
|
+
let event: StreamEvent;
|
|
582
|
+
try {
|
|
583
|
+
event = JSON.parse(data) as StreamEvent;
|
|
584
|
+
} catch {
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
if (event.type === "response.output_item.done") {
|
|
588
|
+
items.push(event.item);
|
|
589
|
+
} else if (
|
|
590
|
+
event.type === "response.completed" ||
|
|
591
|
+
event.type === "response.incomplete"
|
|
592
|
+
) {
|
|
593
|
+
finished = event.response ?? {};
|
|
594
|
+
} else if (event.type === "response.failed") {
|
|
595
|
+
failure = event.response?.error ?? { message: "The response failed" };
|
|
596
|
+
} else if (event.type === "error") {
|
|
597
|
+
failure = { code: event.code, message: event.message };
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (failure || !finished) {
|
|
602
|
+
return Response.json(
|
|
603
|
+
{
|
|
604
|
+
error: {
|
|
605
|
+
message:
|
|
606
|
+
failure?.message ?? "The stream ended before the response finished",
|
|
607
|
+
code: failure?.code,
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
{ status: failure?.code === "context_length_exceeded" ? 400 : 500 },
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
return Response.json({
|
|
614
|
+
...finished,
|
|
615
|
+
output: finished.output?.length ? finished.output : items,
|
|
616
|
+
});
|
|
617
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { type ComponentProps, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { toast } from "@/components/ui/toast";
|
|
6
|
+
import { startChatGptSignInAction } from "@/features/config/config.action";
|
|
7
|
+
import { useServerAction } from "@/lib/protocol/use-server-action";
|
|
8
|
+
|
|
9
|
+
/** How often to look whether the sign-in window was closed, and how long to keep looking: as long as the server listens for its answer. */
|
|
10
|
+
const WINDOW_POLL_MS = 700;
|
|
11
|
+
const WINDOW_WATCH_MS = 10 * 60_000;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Opens ChatGPT's sign-in in a window of its own. The answer lands on the server, which keeps it
|
|
15
|
+
* and signals the screen (`config`); whoever draws this takes it away once signed in. Until then
|
|
16
|
+
* the button waits, and stops waiting when the window is closed without finishing.
|
|
17
|
+
*/
|
|
18
|
+
export function ChatGptSignIn({
|
|
19
|
+
variant,
|
|
20
|
+
}: {
|
|
21
|
+
variant?: ComponentProps<typeof Button>["variant"];
|
|
22
|
+
}) {
|
|
23
|
+
const [waiting, setWaiting] = useState(false);
|
|
24
|
+
const watch = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
25
|
+
const stopWatching = () => {
|
|
26
|
+
if (watch.current) clearInterval(watch.current);
|
|
27
|
+
watch.current = null;
|
|
28
|
+
};
|
|
29
|
+
useEffect(() => stopWatching, []);
|
|
30
|
+
|
|
31
|
+
const [start, starting] = useServerAction(startChatGptSignInAction, {
|
|
32
|
+
onOk: (url) => {
|
|
33
|
+
const popup = window.open(
|
|
34
|
+
url,
|
|
35
|
+
"thursday-chatgpt",
|
|
36
|
+
"popup,width=520,height=720",
|
|
37
|
+
);
|
|
38
|
+
if (!popup) {
|
|
39
|
+
toast.add({
|
|
40
|
+
type: "error",
|
|
41
|
+
title: "The sign-in window was blocked",
|
|
42
|
+
description: "Allow pop-ups for this page, then try again",
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
setWaiting(true);
|
|
47
|
+
stopWatching();
|
|
48
|
+
const deadline = Date.now() + WINDOW_WATCH_MS;
|
|
49
|
+
watch.current = setInterval(() => {
|
|
50
|
+
if (!popup.closed && Date.now() < deadline) return;
|
|
51
|
+
stopWatching();
|
|
52
|
+
setWaiting(false);
|
|
53
|
+
}, WINDOW_POLL_MS);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Button
|
|
59
|
+
variant={variant}
|
|
60
|
+
loading={starting || waiting}
|
|
61
|
+
onClick={() => start()}
|
|
62
|
+
>
|
|
63
|
+
Sign in with ChatGPT
|
|
64
|
+
</Button>
|
|
65
|
+
);
|
|
66
|
+
}
|