thursday-agent 0.1.2 → 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 -1
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +5 -5
- package/.next/routes-manifest.json +27 -3
- 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.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/build-manifest.json +12 -0
- package/.next/server/app/api/bot/memory/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/bot/memory/route.js +14 -0
- package/.next/server/app/api/bot/memory/route.js.map +5 -0
- package/.next/server/app/api/bot/memory/route.js.nft.json +1 -0
- package/.next/server/app/api/bot/memory/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/bot/route.js +7 -7
- package/.next/server/app/api/bot/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/task/[id]/route.js +9 -9
- 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 +6 -6
- 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 +10 -9
- 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 +5 -5
- package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/oauth/callback/route.js +6 -6
- package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/route.js +5 -5
- package/.next/server/app/api/mcp/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/tools/route.js +5 -5
- 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 +7 -8
- 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 +16 -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 +66 -77
- package/.next/server/app/page.js +11 -11
- 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 -1
- 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 +3 -0
- 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 +10 -0
- 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/{_1hzrsvg._.js → _0-73of8._.js} +6 -6
- package/.next/server/chunks/{_1cbk7ms._.js → _0-fjql4._.js} +10 -10
- package/.next/server/chunks/_00xh_q0._.js +27 -0
- package/.next/server/chunks/{_0y8is-s._.js → _03a0897._.js} +1 -1
- package/.next/server/chunks/{_0pppbb5._.js → _03kp0jp._.js} +166 -35
- 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 +74 -44
- package/.next/server/chunks/{_0kl37zl._.js → _0ioz3nd._.js} +1 -1
- package/.next/server/chunks/_0iwfby5._.js +3 -0
- package/.next/server/chunks/{_0hin38_._.js → _0khihjk._.js} +5 -5
- package/.next/server/chunks/_0mecv0f._.js +26 -0
- package/.next/server/chunks/_0mh8j4m._.js +3 -0
- package/.next/server/chunks/{_1icgbi8._.js → _0n74zsf._.js} +2 -2
- package/.next/server/chunks/_0obojvv._.js +3 -0
- package/.next/server/chunks/_0recufk._.js +49 -0
- package/.next/server/chunks/{_1t8fv7b._.js → _0rnf-6n._.js} +2 -2
- package/.next/server/chunks/_0sb7tlz._.js +3 -0
- package/.next/server/chunks/{_0jgm3b8._.js → _0u10wmw._.js} +6 -6
- package/.next/server/chunks/{_12nr4xc._.js → _0u4aiy2._.js} +2 -2
- 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/{_0ks6_sg._.js → _18-1wcc._.js} +6 -6
- package/.next/server/chunks/_1_5mnq4._.js +18 -0
- package/.next/server/chunks/{_0tzfesa._.js → _1_9mjx-._.js} +1 -1
- package/.next/server/chunks/_1d0j961._.js +6 -0
- package/.next/server/chunks/_1d5zmy8._.js +1 -1
- package/.next/server/chunks/_1drgfx4._.js +18 -0
- package/.next/server/chunks/{_1skgy7b._.js → _1e48czp._.js} +5 -5
- 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/{_10qam6o._.js → _1kg70wc._.js} +5 -5
- 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 +1 -1
- package/.next/server/chunks/features_memory_1uqvksn._.js +6 -0
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0tf290y._.js → node_modules_consola_dist_chunks_prompt_mjs_0i7cvml._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0228f43._.js → node_modules_consola_dist_chunks_prompt_mjs_0icqaqw._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0ir-vo2._.js → node_modules_consola_dist_chunks_prompt_mjs_0j61-ma._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0otw8ef._.js → node_modules_consola_dist_chunks_prompt_mjs_0r28-sm._.js} +1 -1
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0v_7drq._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_11zjo-1._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1pycqsq._.js +3 -0
- 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_1y49572._.js +3 -0
- package/.next/server/chunks/ssr/[externals]__0dizgek._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__09q9med._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0_69wqr._.js +3 -0
- 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]__1cyjywu._.js +3 -0
- 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/_0lu1n_x._.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/{_0gd4vaa._.js → _0u2jv_x._.js} +94 -58
- 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/{_0q_2f2x._.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/{_0lc_7c8._.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/{_19_o9d-._.js → _1vi_uo7._.js} +2 -2
- package/.next/server/chunks/ssr/_1vyd1ji._.js +3 -0
- package/.next/server/chunks/ssr/_1xvgn77._.js +3 -0
- 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/lib_realtime_12wg_dd._.js +27 -0
- package/.next/server/chunks/ssr/lib_realtime_15h_v1v._.js +27 -0
- package/.next/server/chunks/ssr/{node_modules_1d-is1o._.js → node_modules_063xwpp._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1ub40gk._.js → node_modules_0bvraul._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1nhw9qg._.js → node_modules_0eu1va6._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0xwx4u-._.js → node_modules_0h50b8n._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_043-236._.js → node_modules_0h5hnr1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1t0y5jt._.js → node_modules_0h7u60n._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1k29j1j._.js → node_modules_0lm7_vm._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_16i4o_3._.js → node_modules_0mwym0g._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1y8_3vg._.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_108wd2o._.js → node_modules_0y_cded._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1jatz7w._.js → node_modules_0z5tsu8._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ls96je._.js → node_modules_0zp9r92._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_06kvvuo._.js → node_modules_1-hc6uv._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1_588_4._.js → node_modules_113tz0l._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ipn1ho._.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_0_j7-g4._.js → node_modules_1_n3ztw._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_20ynonm._.js → node_modules_1bc7gu8._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1hn_sd5._.js → node_modules_1bgb6-f._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0bue6gy._.js → node_modules_1h4a4pp._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0fbw5-u._.js → node_modules_1i7u1ia._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1ee8gav._.js → node_modules_1juq3hs._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0_fvxnt._.js → node_modules_1l0qtdu._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_13f2md6._.js → node_modules_1t9cyee._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0y2-av4._.js → node_modules_1uuj9x1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_00d6675._.js → node_modules_1vqb6po._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1v0077l._.js → node_modules_1xx28h1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_092s8nx._.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_1nvexgu._.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_consola_dist_chunks_prompt_mjs_05cwqv2._.js +47 -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 +67 -78
- 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/1dkjg87pxk0n9.js +28 -0
- package/.next/static/chunks/1ekc7ul556kbl.js +1 -0
- package/.next/static/chunks/{1khakdu_iuedc.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/{2ihkf6l2jowo9.js → 280t0_e99i276.js} +3 -3
- 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/3786h5vayt80-.js +28 -0
- package/.next/static/chunks/{3aquirz5h71da.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.client.ts +24 -0
- package/app/api/events/app-event.server.ts +45 -0
- package/app/api/events/app-event.ts +42 -0
- package/app/api/llm-model/catalog/route.ts +12 -0
- package/app/api/llm-model/credits/route.ts +11 -0
- package/app/api/llm-model/route.ts +26 -0
- 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 +159 -0
- package/app/api/thursday/tool-call/route.ts +67 -0
- package/bin/thursday.mjs +3 -0
- package/components/ui/button-group.tsx +45 -0
- package/components/ui/custom-icon.tsx +259 -0
- package/components/ui/dropdown-menu.tsx +271 -0
- package/components/ui/folded-text.tsx +68 -0
- package/components/ui/input-group.tsx +157 -0
- package/components/ui/json-view.tsx +240 -0
- package/components/ui/shiny-text.tsx +55 -0
- package/components/ui/text-type.tsx +213 -0
- package/components/ui/theme-sync.tsx +9 -0
- package/database/migrations/{20260908030742_rainy_marvel_zombies → 20260910173049_numerous_spirit}/migration.sql +30 -33
- package/database/migrations/{20260908030742_rainy_marvel_zombies → 20260910173049_numerous_spirit}/snapshot.json +124 -145
- package/database/migrations/20260911030320_opposite_jack_power/migration.sql +1 -0
- package/database/migrations/20260911030320_opposite_jack_power/snapshot.json +1158 -0
- package/docs/how-it-works.md +59 -0
- package/docs/images/keeps-going.png +0 -0
- package/docs/images/task-thread.png +0 -0
- package/docs/images/two-minds.png +0 -0
- 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 +105 -39
- package/features/ai/model.schema.ts +232 -34
- package/features/ai/model.ts +169 -24
- package/features/ai/prompts/bot.prompt.ts +245 -59
- package/features/ai/prompts/memory-edit.prompt.ts +33 -0
- package/features/ai/prompts/prompt-helper.ts +75 -30
- package/features/ai/prompts/thursday.prompt.ts +42 -17
- package/features/ai/tools/bot.tool.ts +32 -30
- package/features/ai/tools/connected.ts +17 -4
- package/features/ai/tools/mcp.tool.ts +5 -5
- package/features/ai/tools/memory.tool.ts +142 -74
- package/features/ai/tools/search.tool.ts +136 -47
- package/features/ai/tools/studio.tool.ts +5 -5
- package/features/ai/tools/tool-name.ts +2 -3
- package/features/ai/tools/workspace.tool.ts +33 -4
- package/features/bot/bot.action.ts +24 -7
- package/features/bot/bot.memory.ts +89 -0
- package/features/bot/bot.query.ts +41 -6
- package/features/bot/bot.run.ts +516 -210
- package/features/bot/bot.runner.ts +463 -93
- package/features/bot/bot.schema.ts +84 -7
- package/features/bot/bot.seed.ts +89 -16
- package/features/bot/components/bot-mark.tsx +122 -20
- package/features/bot/components/bot-room.tsx +519 -391
- package/features/bot/components/bot-setting.tsx +832 -341
- 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 +233 -33
- 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 +86 -79
- package/features/memory/memory.action.ts +3 -43
- package/features/memory/memory.edit.ts +87 -0
- package/features/memory/memory.query.ts +33 -44
- package/features/memory/memory.schema.ts +50 -97
- package/features/settings/components/settings.tsx +2 -0
- package/features/settings/settings.alert.ts +3 -1
- package/features/skills/components/skills-setting.tsx +9 -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 +230 -13
- package/hooks/use-draft.ts +37 -0
- package/hooks/use-hotkey.ts +105 -0
- package/hooks/use-object-state.ts +51 -0
- package/hooks/use-on-visible.ts +72 -0
- package/hooks/use-theme.ts +106 -0
- package/hooks/use-wake-word.ts +262 -0
- package/lib/limits.ts +1 -1
- package/lib/oauth-page.ts +27 -0
- package/lib/realtime/openai.ts +2 -2
- package/lib/realtime/realtime.driver.ts +34 -1
- package/lib/realtime/realtime.protocol.ts +7 -1
- package/lib/realtime/realtime.tap.ts +122 -0
- package/lib/sandbox.ts +60 -29
- package/lib/tokens.ts +19 -0
- package/package.json +1 -1
- package/skills/browser/SKILL.md +55 -34
- package/.next/server/app/api/memory/tidy/route/app-paths-manifest.json +0 -3
- package/.next/server/app/api/memory/tidy/route.js +0 -23
- package/.next/server/app/api/memory/tidy/route.js.nft.json +0 -1
- 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]__02kt8yn._.js +0 -10
- 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]__0dupqt7._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0h59di-._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0j_n5l3._.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]__0phdgco._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__0qjqtwo._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0t_dqaj._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0wvgrsl._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0yk6s3w._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0z3gelv._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__10qshki._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__113fqg5._.js +0 -12
- package/.next/server/chunks/[root-of-the-server]__11w_7_3._.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_0q_9w._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1_aoyr9._.js +0 -3
- 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]__1dow0bz._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1e-ci13._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1gmn1cs._.js +0 -10
- package/.next/server/chunks/[root-of-the-server]__1lr7j9t._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__1mzp_el._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1txedx6._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__1u_qvc7._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1zgpz1w._.js +0 -3
- 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/_0zmestz._.js +0 -55
- package/.next/server/chunks/_0zuzmt_._.js +0 -39
- package/.next/server/chunks/_12bbv5i._.js +0 -96
- package/.next/server/chunks/_15q1pig._.js +0 -3
- package/.next/server/chunks/_1drv2et._.js +0 -36
- package/.next/server/chunks/_1ic3bew._.js +0 -5
- package/.next/server/chunks/_1mxn_lb._.js +0 -3
- package/.next/server/chunks/_1uj4_io._.js +0 -10
- package/.next/server/chunks/_1x7p319._.js +0 -6
- package/.next/server/chunks/_1yldgo4._.js +0 -55
- package/.next/server/chunks/_2050gwn._.js +0 -255
- package/.next/server/chunks/_next-internal_server_app_api_memory_tidy_route_actions_0_cfuom.js +0 -3
- package/.next/server/chunks/features_0scmr4i._.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_0vorg6w._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0ykklj0._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1ryg3wh._.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_1vwbnq_._.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]__01id-v3._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0b-0i32._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0dfwvr4._.js +0 -4
- package/.next/server/chunks/ssr/[root-of-the-server]__0iuoa-r._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1-mnhyd._.js +0 -90
- package/.next/server/chunks/ssr/[root-of-the-server]__185grso._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1minz8_._.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/_09-0k3z._.js +0 -3
- package/.next/server/chunks/ssr/_0ay6b01._.js +0 -3
- package/.next/server/chunks/ssr/_0cvjbuz._.js +0 -3
- package/.next/server/chunks/ssr/_0fafgvl._.js +0 -3
- package/.next/server/chunks/ssr/_0j4v8of._.js +0 -3
- package/.next/server/chunks/ssr/_0l81bzy._.js +0 -3
- package/.next/server/chunks/ssr/_0nso8tp._.js +0 -3
- package/.next/server/chunks/ssr/_0sf1isi._.js +0 -3
- package/.next/server/chunks/ssr/_0ti51xc._.js +0 -3
- package/.next/server/chunks/ssr/_0y84qo6._.js +0 -3
- package/.next/server/chunks/ssr/_10uvw3l._.js +0 -3
- package/.next/server/chunks/ssr/_1dcxzqe._.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/_1jbv7m1._.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/lib_realtime_0aetuyp._.js +0 -24
- package/.next/server/chunks/ssr/lib_realtime_18k_aj-._.js +0 -24
- package/.next/server/chunks/ssr/node_modules_zod_v4_classic_errors_05xv7vp.js +0 -3
- package/.next/server/chunks/ssr/node_modules_zod_v4_classic_external_15fmmz3.js +0 -55
- package/.next/static/chunks/0f9sq-7q_yxyl.js +0 -1
- package/.next/static/chunks/0tydnn2kybibk.js +0 -1
- package/.next/static/chunks/0yysv1gzin17t.js +0 -1
- package/.next/static/chunks/1fr5m75pwtyic.js +0 -1
- package/.next/static/chunks/1gy3bbv7usnjo.js +0 -1
- package/.next/static/chunks/1keib_gt066gc.js +0 -1
- package/.next/static/chunks/1ytnip8wdng93.js +0 -1
- package/.next/static/chunks/21rk8w7bda-su.js +0 -1
- package/.next/static/chunks/247d-r95j8xs0.js +0 -1
- package/.next/static/chunks/28g42tw8tsvhd.js +0 -1
- package/.next/static/chunks/29gyb3nubj51j.js +0 -53
- package/.next/static/chunks/2cwjgkt_hoyhj.js +0 -22
- package/.next/static/chunks/2fq_86jvq9ymq.js +0 -2
- package/.next/static/chunks/2rbf1fpd8n80-.js +0 -22
- package/.next/static/chunks/2ukj8k69y89um.js +0 -1
- package/.next/static/chunks/2uxum7qbyeyxd.js +0 -1
- package/.next/static/chunks/2wngeetfnzxkp.js +0 -2
- package/.next/static/chunks/388-t2v72jh39.css +0 -1
- package/.next/static/chunks/3fqvc0cg-atq_.js +0 -1
- package/.next/static/chunks/3p4egvfjxpatb.js +0 -1
- package/.next/static/chunks/3qe1w1ei-huhl.js +0 -1
- package/.next/static/chunks/3xxa2j56-f3nm.js +0 -3
- package/.next/static/chunks/3y40riycdip-i.js +0 -1
- package/.next/static/chunks/45a6zbtkelph9.js +0 -1
- package/.next/static/media/favicon.2vob68tjqpejf.ico +0 -0
- package/features/ai/prompts/memory-tidy.prompt.ts +0 -123
- package/features/ai/tools/tidy.tool.ts +0 -22
- 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/{memory/tidy → bot/memory/files}/route/build-manifest.json +0 -0
- /package/.next/server/app/api/{memory/tidy → bot/memory/files}/route/server-reference-manifest.json +0 -0
- /package/.next/server/app/api/{memory/tidy → bot/memory/files}/route.js.map +0 -0
- /package/.next/static/{X34BUd9bfdH27ripuwwBm → x1cCqY0kWpR_VcYx_lWO5}/_buildManifest.js +0 -0
- /package/.next/static/{X34BUd9bfdH27ripuwwBm → x1cCqY0kWpR_VcYx_lWO5}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{X34BUd9bfdH27ripuwwBm → x1cCqY0kWpR_VcYx_lWO5}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# How it works
|
|
2
|
+
|
|
3
|
+
The README says what Thursday is. This says what each part may touch, so you can guess what will happen before you say it.
|
|
4
|
+
|
|
5
|
+
## The call
|
|
6
|
+
|
|
7
|
+
A call is a session with a realtime speech model — OpenAI Realtime or xAI Grok Voice, whichever key you gave. Audio in, audio out, interruptions and all. The model hears a prompt assembled the moment the line opens: who she is, what she remembers about you, which bots exist, what was said on the last calls.
|
|
8
|
+
|
|
9
|
+
She holds three things and nothing else:
|
|
10
|
+
|
|
11
|
+
- **Memory.** A folder of notes about you. She reads a note before answering out of it and writes the moment something worth keeping comes up — one fact per line, dates as dates. A rule you lay down ("answer in Korean", "keep it short") is carried into every call.
|
|
12
|
+
- **One command.** A shell, for the things that take a second: open a file, play something, look at what is in a folder. One command is something she does, not a job she hands over.
|
|
13
|
+
- **The roster.** The bots, by name, and what each is for.
|
|
14
|
+
|
|
15
|
+
Everything else — anything that takes more than a few seconds — she hands to a bot with `delegate` and keeps talking. That is deliberate: the call must never go silent, and a speech model that runs a browser would be silent for minutes.
|
|
16
|
+
|
|
17
|
+
When a job comes back, it arrives as a system note, not as you speaking. She tells you in one sentence, in her own words. A file the job made opens on your screen by itself; a question the job asks becomes buttons on screen and words in your ear.
|
|
18
|
+
|
|
19
|
+
## Bots
|
|
20
|
+
|
|
21
|
+
A bot is a text model — any of OpenAI, Anthropic, Google, xAI, or whatever the Vercel AI Gateway carries — with the whole machine:
|
|
22
|
+
|
|
23
|
+
- a **shell** in a workspace folder, with no pager and no secrets in its environment
|
|
24
|
+
- a **real browser** (Chromium through Playwright), headless until a sign-in or something you should see, and able to attach to the Chrome you already use
|
|
25
|
+
- the **filesystem**, read anywhere, written only inside the workspace and where you pointed it
|
|
26
|
+
- **skills** — written-down methods it reads before starting: the browser, this Mac, building a page, finding and writing skills
|
|
27
|
+
- **MCP servers** you connected, searched by name and called by schema, with OAuth handled
|
|
28
|
+
- a **studio** of image, video, speech and transcription models
|
|
29
|
+
- **each other**: a bot hands a part of its job to the bot that exists for it, and folds the answer into its own report
|
|
30
|
+
|
|
31
|
+
A job ends in the thing that was asked for — the account made, the page built, the comparison in a table — and a report, written to be heard, because she reads it out loud. Anything that does not fit in a few lines is a file under `artifacts/`, and the report names it.
|
|
32
|
+
|
|
33
|
+
What only you can give — a password, a one-time code, a passkey, a decision between two real options — stops the job at that point. The bot sets it up one action away, asks, and continues when the answer comes.
|
|
34
|
+
|
|
35
|
+
Three bots come with the app, split by temperament: **Jarvis** plans a job, hands parts to whoever fits and does the rest; **Navigator** goes and looks; **Scribe** writes. Make more in settings: a name, a sentence about what it is for, optionally a model and a few pinned tools. The sentence is what she reads when deciding who gets the job.
|
|
36
|
+
|
|
37
|
+
## Jobs outlive the call
|
|
38
|
+
|
|
39
|
+
A job runs on the server. It is not in the tab, and it does not stop when you hang up. Every step — the request, each tool call, what came back, the report — is written as a row, and the screen is a projection of those rows. When you come back, the jobs that finished are waiting, and she tells you about them on the next call, or rings you if you turned that on.
|
|
40
|
+
|
|
41
|
+
A job that stopped to ask, or ran out of its step budget, is resumed from where it stopped: the same thread, with your answer appended. A long thread is compacted — the model summarizes what happened so far and continues from the summary.
|
|
42
|
+
|
|
43
|
+
## Memory is a folder
|
|
44
|
+
|
|
45
|
+
Memory is plain notes on your disk, in the data folder (`~/.thursday` when installed with `npx`, the checkout otherwise). Each note is a topic — `profile`, `preferences`, `people/…`, and whatever she creates. A few are `alwaysLoad` and travel into every call: your name, your language, how you like to be addressed, the rules you gave her.
|
|
46
|
+
|
|
47
|
+
You can open any note from settings and read exactly what she knows. When the listing gets long she says so in a lull, puts a note on your screen and forgets only what you name.
|
|
48
|
+
|
|
49
|
+
Writing mid-conversation catches what she noticed; it misses what only reads back as important afterwards. So when a call ends, a text model re-reads the most recent turns and reconciles the notes with them — correcting what changed, adding what was said in passing. Calls are stamped as they are read, so a backlog of short calls collapses into one pass rather than one per call.
|
|
50
|
+
|
|
51
|
+
Bots read memory but do not write it. What a bot learns goes into its report; whether it is worth keeping is her call, with you.
|
|
52
|
+
|
|
53
|
+
## What is not here
|
|
54
|
+
|
|
55
|
+
- **No cloud.** The server is the one on your machine; keys are stored locally and sent only to the provider you chose.
|
|
56
|
+
- **No account.** There is nothing to sign up for. There is also no login, which is why the server binds to `127.0.0.1` — see [SECURITY.md](../SECURITY.md).
|
|
57
|
+
- **No prompt engineering on your side.** The prompts are assembled from your data on every session; the one thing you can add is an instruction to her, and one to each bot, in settings.
|
|
58
|
+
|
|
59
|
+
For the code — which file owns what, and why — read [CLAUDE.md](../CLAUDE.md).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
+
}
|