@fastagent-sh/fastagent 0.24.4 → 0.25.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.
Files changed (480) hide show
  1. package/README.md +53 -82
  2. package/dist/agent-api.d.ts +452 -0
  3. package/dist/agent-api.js +201 -0
  4. package/dist/answer-text.d.ts +8 -0
  5. package/dist/answer-text.js +18 -0
  6. package/dist/atomic-write.d.ts +2 -2
  7. package/dist/atomic-write.js +2 -2
  8. package/dist/authoring.d.ts +77 -0
  9. package/dist/authoring.js +238 -0
  10. package/dist/channel.d.ts +29 -15
  11. package/dist/channel.js +4 -1
  12. package/dist/channels/agentcore-protocol.d.ts +36 -50
  13. package/dist/channels/agentcore-protocol.js +3 -3
  14. package/dist/channels/agentcore-service.d.ts +8 -18
  15. package/dist/channels/agentcore-service.js +108 -127
  16. package/dist/channels/agentcore.d.ts +8 -24
  17. package/dist/channels/agentcore.js +32 -65
  18. package/dist/channels/body.d.ts +0 -16
  19. package/dist/channels/body.js +0 -25
  20. package/dist/channels/busy.js +6 -1
  21. package/dist/channels/context.d.ts +4 -0
  22. package/dist/channels/context.js +35 -0
  23. package/dist/channels/discover.d.ts +18 -20
  24. package/dist/channels/discover.js +41 -99
  25. package/dist/channels/feishu/feishu-api.d.ts +54 -4
  26. package/dist/channels/feishu/feishu-api.js +192 -149
  27. package/dist/channels/feishu/feishu.d.ts +3 -0
  28. package/dist/channels/feishu/feishu.js +117 -106
  29. package/dist/channels/feishu/history.d.ts +55 -0
  30. package/dist/channels/feishu/history.js +199 -0
  31. package/dist/channels/feishu/invoke-turn.d.ts +13 -11
  32. package/dist/channels/feishu/invoke-turn.js +9 -10
  33. package/dist/channels/feishu/model.d.ts +2 -1
  34. package/dist/channels/feishu/normalize.js +100 -0
  35. package/dist/channels/feishu/preview.d.ts +2 -2
  36. package/dist/channels/feishu/register-webhook.d.ts +1 -0
  37. package/dist/channels/feishu/register-webhook.js +3 -2
  38. package/dist/channels/feishu/scaffold/channel.ts +65 -46
  39. package/dist/channels/feishu/scaffold/feishu-send.ts +38 -33
  40. package/dist/channels/feishu/scaffold/feishu-threads.ts +25 -0
  41. package/dist/channels/feishu/setup-mode.d.ts +45 -25
  42. package/dist/channels/feishu/setup-mode.js +74 -23
  43. package/dist/channels/feishu/shared-api.d.ts +24 -6
  44. package/dist/channels/feishu/shared-api.js +30 -24
  45. package/dist/channels/kit/context-buffer.d.ts +11 -2
  46. package/dist/channels/kit/context-buffer.js +7 -4
  47. package/dist/channels/kit/delivery.d.ts +19 -3
  48. package/dist/channels/kit/delivery.js +74 -15
  49. package/dist/channels/kit/event-stream.js +2 -1
  50. package/dist/channels/kit/invoke-turn-kit.d.ts +41 -41
  51. package/dist/channels/kit/invoke-turn-kit.js +62 -71
  52. package/dist/channels/kit/place-history.d.ts +107 -0
  53. package/dist/channels/kit/place-history.js +182 -0
  54. package/dist/channels/kit/preview-kit.d.ts +9 -5
  55. package/dist/channels/kit/preview-kit.js +36 -15
  56. package/dist/channels/kit/room-threads.d.ts +46 -0
  57. package/dist/channels/kit/room-threads.js +81 -0
  58. package/dist/channels/kit/seen.js +1 -1
  59. package/dist/channels/kit/stop-command.d.ts +6 -3
  60. package/dist/channels/kit/stop-command.js +20 -18
  61. package/dist/channels/kit/thread-fork.d.ts +17 -0
  62. package/dist/channels/kit/thread-fork.js +43 -0
  63. package/dist/channels/kit/thread-participants.js +1 -1
  64. package/dist/channels/kit/transport.d.ts +5 -0
  65. package/dist/channels/kit/transport.js +5 -0
  66. package/dist/channels/kit/turn-runner.d.ts +10 -9
  67. package/dist/channels/kit/turn-runner.js +17 -19
  68. package/dist/channels/kit/turn-store.d.ts +5 -4
  69. package/dist/channels/kit/turn-store.js +5 -5
  70. package/dist/channels/lark/lark.d.ts +3 -0
  71. package/dist/channels/lark/lark.js +5 -0
  72. package/dist/channels/lark/onboard.d.ts +5 -3
  73. package/dist/channels/lark/onboard.js +14 -14
  74. package/dist/channels/lark/scaffold/channel.ts +67 -44
  75. package/dist/channels/lark/scaffold/lark-send.ts +38 -33
  76. package/dist/channels/lark/scaffold/lark-threads.ts +25 -0
  77. package/dist/channels/respond.d.ts +0 -4
  78. package/dist/channels/respond.js +1 -1
  79. package/dist/channels/router.d.ts +18 -0
  80. package/dist/channels/router.js +68 -0
  81. package/dist/channels/serve.d.ts +5 -70
  82. package/dist/channels/serve.js +105 -287
  83. package/dist/channels/slack/history.d.ts +42 -0
  84. package/dist/channels/slack/history.js +282 -0
  85. package/dist/channels/slack/invoke-turn.d.ts +12 -5
  86. package/dist/channels/slack/invoke-turn.js +7 -5
  87. package/dist/channels/slack/manifest.d.ts +1 -4
  88. package/dist/channels/slack/manifest.js +22 -10
  89. package/dist/channels/slack/model.d.ts +2 -1
  90. package/dist/channels/slack/onboard.d.ts +0 -2
  91. package/dist/channels/slack/onboard.js +2 -4
  92. package/dist/channels/slack/onboarding-state.d.ts +0 -2
  93. package/dist/channels/slack/onboarding-state.js +0 -1
  94. package/dist/channels/slack/parse.d.ts +0 -5
  95. package/dist/channels/slack/parse.js +1 -16
  96. package/dist/channels/slack/preview.d.ts +2 -3
  97. package/dist/channels/slack/preview.js +78 -52
  98. package/dist/channels/slack/register-webhook.d.ts +1 -0
  99. package/dist/channels/slack/register-webhook.js +6 -5
  100. package/dist/channels/slack/scaffold/channel.ts +23 -24
  101. package/dist/channels/slack/scaffold/slack-send.ts +50 -43
  102. package/dist/channels/slack/scaffold/slack-threads.ts +29 -0
  103. package/dist/channels/slack/setup-server.d.ts +19 -0
  104. package/dist/channels/slack/setup-server.js +113 -11
  105. package/dist/channels/slack/shared-api.d.ts +15 -4
  106. package/dist/channels/slack/shared-api.js +19 -14
  107. package/dist/channels/slack/slack-api.d.ts +61 -2
  108. package/dist/channels/slack/slack-api.js +28 -5
  109. package/dist/channels/slack/slack.js +97 -52
  110. package/dist/channels/slack/welcomed.js +1 -1
  111. package/dist/channels/telegram/context-buffer.d.ts +6 -1
  112. package/dist/channels/telegram/context-buffer.js +19 -0
  113. package/dist/channels/telegram/invoke-turn.d.ts +8 -4
  114. package/dist/channels/telegram/invoke-turn.js +7 -7
  115. package/dist/channels/telegram/parse.d.ts +7 -2
  116. package/dist/channels/telegram/parse.js +7 -0
  117. package/dist/channels/telegram/preview.d.ts +2 -2
  118. package/dist/channels/telegram/register-webhook.d.ts +1 -0
  119. package/dist/channels/telegram/register-webhook.js +3 -2
  120. package/dist/channels/telegram/scaffold/channel.ts +27 -28
  121. package/dist/channels/telegram/scaffold/telegram-send.ts +61 -108
  122. package/dist/channels/telegram/shared-api.d.ts +34 -0
  123. package/dist/channels/telegram/shared-api.js +79 -0
  124. package/dist/channels/telegram/telegram-api.d.ts +15 -5
  125. package/dist/channels/telegram/telegram-api.js +10 -6
  126. package/dist/channels/telegram/telegram.js +27 -21
  127. package/dist/channels/telegram/turn-store.js +1 -1
  128. package/dist/channels/{kit/turn-queue.d.ts → turn-queue.d.ts} +2 -2
  129. package/dist/channels/{kit/turn-queue.js → turn-queue.js} +10 -10
  130. package/dist/cli/add-feishu.d.ts +42 -12
  131. package/dist/cli/add-feishu.js +177 -124
  132. package/dist/cli/add-slack.d.ts +8 -3
  133. package/dist/cli/add-slack.js +115 -63
  134. package/dist/cli/auth-view.d.ts +10 -3
  135. package/dist/cli/auth-view.js +8 -6
  136. package/dist/cli/box-login.d.ts +17 -7
  137. package/dist/cli/box-login.js +46 -12
  138. package/dist/cli/commands/add.d.ts +15 -4
  139. package/dist/cli/commands/add.js +252 -143
  140. package/dist/cli/commands/auth.d.ts +6 -0
  141. package/dist/cli/commands/auth.js +50 -0
  142. package/dist/cli/commands/content.d.ts +6 -0
  143. package/dist/cli/commands/content.js +45 -0
  144. package/dist/cli/commands/deploy/agentcore.js +50 -77
  145. package/dist/cli/commands/deploy/docker.js +38 -39
  146. package/dist/cli/commands/deploy/fly.js +21 -18
  147. package/dist/cli/commands/deploy/host-login.d.ts +26 -0
  148. package/dist/cli/commands/deploy/host-login.js +90 -0
  149. package/dist/cli/commands/deploy/railway.js +21 -26
  150. package/dist/cli/commands/deploy/shared.d.ts +45 -17
  151. package/dist/cli/commands/deploy/shared.js +34 -15
  152. package/dist/cli/commands/deploy.d.ts +0 -4
  153. package/dist/cli/commands/deploy.js +237 -50
  154. package/dist/cli/commands/destroy.js +8 -6
  155. package/dist/cli/commands/dev.d.ts +0 -2
  156. package/dist/cli/commands/dev.js +20 -19
  157. package/dist/cli/commands/env.d.ts +12 -0
  158. package/dist/cli/commands/env.js +190 -0
  159. package/dist/cli/commands/info.js +95 -156
  160. package/dist/cli/commands/init.d.ts +2 -2
  161. package/dist/cli/commands/init.js +35 -33
  162. package/dist/cli/commands/invoke.d.ts +6 -0
  163. package/dist/cli/commands/invoke.js +131 -16
  164. package/dist/cli/commands/login.d.ts +31 -2
  165. package/dist/cli/commands/login.js +413 -64
  166. package/dist/cli/commands/logout.d.ts +4 -0
  167. package/dist/cli/commands/logout.js +53 -0
  168. package/dist/cli/commands/logs.js +8 -6
  169. package/dist/cli/commands/models.d.ts +2 -1
  170. package/dist/cli/commands/models.js +39 -37
  171. package/dist/cli/commands/schedules.d.ts +21 -0
  172. package/dist/cli/commands/schedules.js +143 -0
  173. package/dist/cli/commands/start.d.ts +11 -10
  174. package/dist/cli/commands/start.js +35 -62
  175. package/dist/cli/content-view.d.ts +9 -0
  176. package/dist/cli/content-view.js +26 -0
  177. package/dist/cli/fail.d.ts +9 -12
  178. package/dist/cli/fail.js +17 -14
  179. package/dist/cli/invoke-stream.d.ts +12 -3
  180. package/dist/cli/invoke-stream.js +85 -23
  181. package/dist/cli/kernel.d.ts +9 -2
  182. package/dist/cli/kernel.js +15 -6
  183. package/dist/cli/login-relay.d.ts +3 -2
  184. package/dist/cli/login-relay.js +18 -6
  185. package/dist/cli/models-view.d.ts +5 -1
  186. package/dist/cli/models-view.js +1 -1
  187. package/dist/cli/person.d.ts +10 -0
  188. package/dist/cli/person.js +23 -0
  189. package/dist/cli/program.js +421 -203
  190. package/dist/cli/serve.d.ts +13 -63
  191. package/dist/cli/serve.js +31 -121
  192. package/dist/cli/shared.d.ts +21 -48
  193. package/dist/cli/shared.js +68 -89
  194. package/dist/cli/ticket.d.ts +136 -0
  195. package/dist/cli/ticket.js +476 -0
  196. package/dist/client.d.ts +7 -0
  197. package/dist/client.js +2 -0
  198. package/dist/content/declare.d.ts +70 -0
  199. package/dist/content/declare.js +163 -0
  200. package/dist/content/file.d.ts +32 -0
  201. package/dist/content/file.js +119 -0
  202. package/dist/content/git.d.ts +41 -0
  203. package/dist/content/git.js +217 -0
  204. package/dist/content/mount.d.ts +2 -0
  205. package/dist/content/mount.js +15 -0
  206. package/dist/content/resolve.d.ts +52 -0
  207. package/dist/content/resolve.js +151 -0
  208. package/dist/content/source.d.ts +27 -0
  209. package/dist/content/source.js +53 -0
  210. package/dist/conversation-sessions.d.ts +26 -0
  211. package/dist/conversation-sessions.js +99 -0
  212. package/dist/declared-secrets.d.ts +19 -37
  213. package/dist/declared-secrets.js +16 -58
  214. package/dist/deploy/agentcore/destroy.d.ts +3 -3
  215. package/dist/deploy/agentcore/destroy.js +46 -28
  216. package/dist/deploy/agentcore/forwarder.js +105 -22
  217. package/dist/deploy/agentcore/logs.js +1 -1
  218. package/dist/deploy/agentcore/plan.d.ts +10 -24
  219. package/dist/deploy/agentcore/plan.js +31 -135
  220. package/dist/deploy/agentcore/run.d.ts +1 -1
  221. package/dist/deploy/agentcore/run.js +8 -5
  222. package/dist/deploy/agentcore/shell.d.ts +1 -1
  223. package/dist/deploy/agentcore/shell.js +5 -5
  224. package/dist/deploy/api.d.ts +34 -0
  225. package/dist/deploy/api.js +50 -0
  226. package/dist/deploy/box-shell.d.ts +6 -0
  227. package/dist/deploy/box-shell.js +7 -0
  228. package/dist/deploy/build-context.d.ts +7 -5
  229. package/dist/deploy/build-context.js +41 -35
  230. package/dist/deploy/channel-ingress.d.ts +6 -1
  231. package/dist/deploy/channel-ingress.js +18 -5
  232. package/dist/deploy/container.d.ts +22 -9
  233. package/dist/deploy/container.js +96 -55
  234. package/dist/deploy/docker/plan.d.ts +3 -2
  235. package/dist/deploy/docker/plan.js +13 -14
  236. package/dist/deploy/docker/run.d.ts +2 -2
  237. package/dist/deploy/docker/run.js +8 -4
  238. package/dist/deploy/fly/plan.d.ts +1 -1
  239. package/dist/deploy/fly/plan.js +12 -21
  240. package/dist/deploy/fly/run.d.ts +10 -6
  241. package/dist/deploy/fly/run.js +11 -6
  242. package/dist/deploy/hosts.d.ts +2 -1
  243. package/dist/deploy/hosts.js +2 -1
  244. package/dist/deploy/preflight.d.ts +21 -23
  245. package/dist/deploy/preflight.js +222 -201
  246. package/dist/deploy/railway/plan.d.ts +8 -6
  247. package/dist/deploy/railway/plan.js +15 -58
  248. package/dist/deploy/railway/run.d.ts +8 -3
  249. package/dist/deploy/railway/run.js +12 -8
  250. package/dist/deploy/residency.d.ts +5 -11
  251. package/dist/deploy/residency.js +0 -6
  252. package/dist/deploy/runner.d.ts +1 -1
  253. package/dist/deploy/runner.js +1 -1
  254. package/dist/deploy/secrets.d.ts +16 -2
  255. package/dist/deploy/secrets.js +33 -6
  256. package/dist/deploy/workspace.d.ts +11 -6
  257. package/dist/deploy/workspace.js +55 -38
  258. package/dist/dev-supervisor.d.ts +11 -2
  259. package/dist/dev-supervisor.js +51 -26
  260. package/dist/effect-port.d.ts +4 -4
  261. package/dist/effect-port.js +4 -4
  262. package/dist/environment/apt.d.ts +4 -0
  263. package/dist/environment/apt.js +32 -0
  264. package/dist/environment/declare.d.ts +59 -0
  265. package/dist/environment/declare.js +210 -0
  266. package/dist/environment/mise.d.ts +50 -0
  267. package/dist/environment/mise.js +268 -0
  268. package/dist/feishu.d.ts +2 -2
  269. package/dist/feishu.js +2 -2
  270. package/dist/git.d.ts +13 -0
  271. package/dist/git.js +55 -0
  272. package/dist/harnesses/models/catalog.d.ts +20 -0
  273. package/dist/harnesses/models/catalog.js +254 -0
  274. package/dist/{engines/pi/auth.d.ts → harnesses/models/credentials.d.ts} +3 -3
  275. package/dist/{engines/pi/auth.js → harnesses/models/credentials.js} +3 -3
  276. package/dist/harnesses/models/environment.d.ts +43 -0
  277. package/dist/harnesses/models/environment.js +82 -0
  278. package/dist/{engines/pi → harnesses/models}/login.d.ts +57 -26
  279. package/dist/{engines/pi → harnesses/models}/login.js +187 -79
  280. package/dist/harnesses/models/models.d.ts +83 -0
  281. package/dist/harnesses/models/models.js +158 -0
  282. package/dist/harnesses/pi-durable/agent.d.ts +42 -0
  283. package/dist/harnesses/pi-durable/agent.js +579 -0
  284. package/dist/harnesses/pi-durable/defaults.d.ts +28 -0
  285. package/dist/harnesses/pi-durable/defaults.js +26 -0
  286. package/dist/harnesses/pi-durable/define.d.ts +77 -0
  287. package/dist/harnesses/pi-durable/define.js +28 -0
  288. package/dist/harnesses/pi-durable/entries.d.ts +32 -0
  289. package/dist/harnesses/pi-durable/entries.js +126 -0
  290. package/dist/harnesses/pi-durable/events.d.ts +25 -0
  291. package/dist/harnesses/pi-durable/events.js +238 -0
  292. package/dist/harnesses/pi-durable/ids.d.ts +16 -0
  293. package/dist/harnesses/pi-durable/ids.js +55 -0
  294. package/dist/harnesses/pi-durable/messages.d.ts +41 -0
  295. package/dist/harnesses/pi-durable/messages.js +116 -0
  296. package/dist/harnesses/pi-durable/open.d.ts +88 -0
  297. package/dist/harnesses/pi-durable/open.js +329 -0
  298. package/dist/harnesses/pi-durable/reads.d.ts +30 -0
  299. package/dist/harnesses/pi-durable/reads.js +94 -0
  300. package/dist/harnesses/pi-durable/registry.d.ts +18 -0
  301. package/dist/harnesses/pi-durable/registry.js +56 -0
  302. package/dist/harnesses/pi-durable/release.d.ts +13 -0
  303. package/dist/harnesses/pi-durable/release.js +40 -0
  304. package/dist/harnesses/pi-durable/serve.d.ts +80 -0
  305. package/dist/harnesses/pi-durable/serve.js +156 -0
  306. package/dist/harnesses/pi-durable/sessions.d.ts +27 -0
  307. package/dist/harnesses/pi-durable/sessions.js +47 -0
  308. package/dist/harnesses/pi-durable/storage.d.ts +4 -0
  309. package/dist/harnesses/pi-durable/storage.js +49 -0
  310. package/dist/harnesses/pi-durable/stream.d.ts +20 -0
  311. package/dist/harnesses/pi-durable/stream.js +221 -0
  312. package/dist/harnesses/pi-durable/text.d.ts +30 -0
  313. package/dist/harnesses/pi-durable/text.js +292 -0
  314. package/dist/harnesses/pi-durable/wake.d.ts +4 -0
  315. package/dist/harnesses/pi-durable/wake.js +81 -0
  316. package/dist/http/client.d.ts +6 -0
  317. package/dist/http/client.js +256 -0
  318. package/dist/http/contract.d.ts +541 -0
  319. package/dist/http/contract.js +179 -0
  320. package/dist/http/server.d.ts +34 -0
  321. package/dist/http/server.js +194 -0
  322. package/dist/index.d.ts +17 -4
  323. package/dist/index.js +10 -5
  324. package/dist/lark.d.ts +2 -2
  325. package/dist/lark.js +2 -2
  326. package/dist/locked-file.d.ts +17 -0
  327. package/dist/{engines/pi/locked-file.js → locked-file.js} +16 -10
  328. package/dist/once.d.ts +6 -0
  329. package/dist/once.js +23 -0
  330. package/dist/paths.d.ts +36 -52
  331. package/dist/paths.js +66 -182
  332. package/dist/runtime.d.ts +25 -2
  333. package/dist/runtime.js +27 -0
  334. package/dist/scaffold/add-channel.d.ts +14 -27
  335. package/dist/scaffold/add-channel.js +82 -180
  336. package/dist/scaffold/init.d.ts +17 -18
  337. package/dist/scaffold/init.js +87 -99
  338. package/dist/scaffold/templates/SYSTEM.md +12 -0
  339. package/dist/scaffold/templates/env.example +4 -10
  340. package/dist/scaffold/templates/fastagent.config.ts +21 -33
  341. package/dist/scaffold/templates/gitignore +8 -1
  342. package/dist/scaffold/templates/skills/writing-great-skills/SKILL.md +1 -2
  343. package/dist/scaffold/templates.d.ts +9 -4
  344. package/dist/scaffold/templates.js +7 -5
  345. package/dist/scaffold/vendor-skill.d.ts +1 -4
  346. package/dist/scaffold/vendor-skill.js +5 -6
  347. package/dist/schedule/cron.d.ts +13 -0
  348. package/dist/schedule/cron.js +52 -0
  349. package/dist/schedule/eventbridge-cron.d.ts +17 -0
  350. package/dist/schedule/eventbridge-cron.js +88 -0
  351. package/dist/schedule/schedule.d.ts +23 -0
  352. package/dist/schedule/schedule.js +34 -0
  353. package/dist/schedule/scheduler.d.ts +34 -33
  354. package/dist/schedule/scheduler.js +251 -137
  355. package/dist/schedule/state.d.ts +4 -14
  356. package/dist/schedule/state.js +7 -7
  357. package/dist/schedule/wake-alarm.d.ts +22 -2
  358. package/dist/schedule/wake-alarm.js +66 -20
  359. package/dist/schedule/wakeups.d.ts +16 -15
  360. package/dist/schedule/wakeups.js +47 -45
  361. package/dist/service.d.ts +56 -169
  362. package/dist/service.js +91 -187
  363. package/dist/serving-command.d.ts +10 -0
  364. package/dist/serving-command.js +16 -0
  365. package/dist/slack.d.ts +1 -1
  366. package/dist/slack.js +1 -1
  367. package/dist/{channels/kit/state.js → state-file.js} +6 -6
  368. package/dist/telegram.d.ts +1 -0
  369. package/dist/telegram.js +1 -0
  370. package/dist/tunnel.d.ts +3 -0
  371. package/dist/tunnel.js +12 -4
  372. package/dist/value-file.d.ts +21 -0
  373. package/dist/value-file.js +108 -0
  374. package/dist/version.d.ts +5 -0
  375. package/dist/version.js +14 -1
  376. package/package.json +18 -28
  377. package/dist/agent.d.ts +0 -106
  378. package/dist/agent.js +0 -21
  379. package/dist/channels/control.d.ts +0 -31
  380. package/dist/channels/control.js +0 -329
  381. package/dist/channels/define-channel.d.ts +0 -39
  382. package/dist/channels/define-channel.js +0 -9
  383. package/dist/channels/feishu/context-buffer.d.ts +0 -35
  384. package/dist/channels/feishu/context-buffer.js +0 -65
  385. package/dist/channels/feishu/scaffold/channel.websocket.ts +0 -31
  386. package/dist/channels/http.d.ts +0 -23
  387. package/dist/channels/http.js +0 -57
  388. package/dist/channels/lark/scaffold/channel.websocket.ts +0 -31
  389. package/dist/channels/slack/context-buffer.d.ts +0 -23
  390. package/dist/channels/slack/context-buffer.js +0 -37
  391. package/dist/channels/sse.d.ts +0 -4
  392. package/dist/channels/sse.js +0 -66
  393. package/dist/cli/commands/chat.d.ts +0 -3
  394. package/dist/cli/commands/chat.js +0 -14
  395. package/dist/cli/commands/routine-run.d.ts +0 -6
  396. package/dist/cli/commands/routine-run.js +0 -46
  397. package/dist/cli/commands/routine.d.ts +0 -25
  398. package/dist/cli/commands/routine.js +0 -122
  399. package/dist/cli/commands/tool.d.ts +0 -1
  400. package/dist/cli/commands/tool.js +0 -57
  401. package/dist/collect.d.ts +0 -63
  402. package/dist/collect.js +0 -90
  403. package/dist/core.d.ts +0 -13
  404. package/dist/core.js +0 -15
  405. package/dist/engines/pi/agent-models.d.ts +0 -83
  406. package/dist/engines/pi/agent-models.js +0 -120
  407. package/dist/engines/pi/agent-session-factory.d.ts +0 -121
  408. package/dist/engines/pi/agent-session-factory.js +0 -535
  409. package/dist/engines/pi/chat.d.ts +0 -3
  410. package/dist/engines/pi/chat.js +0 -8
  411. package/dist/engines/pi/config.d.ts +0 -71
  412. package/dist/engines/pi/config.js +0 -199
  413. package/dist/engines/pi/create.d.ts +0 -170
  414. package/dist/engines/pi/create.js +0 -343
  415. package/dist/engines/pi/definition.d.ts +0 -74
  416. package/dist/engines/pi/definition.js +0 -236
  417. package/dist/engines/pi/entry-images.d.ts +0 -14
  418. package/dist/engines/pi/entry-images.js +0 -23
  419. package/dist/engines/pi/invoke-session.d.ts +0 -18
  420. package/dist/engines/pi/invoke-session.js +0 -403
  421. package/dist/engines/pi/locked-file.d.ts +0 -9
  422. package/dist/engines/pi/machine.d.ts +0 -53
  423. package/dist/engines/pi/machine.js +0 -137
  424. package/dist/engines/pi/models.d.ts +0 -200
  425. package/dist/engines/pi/models.js +0 -533
  426. package/dist/engines/pi/open.d.ts +0 -180
  427. package/dist/engines/pi/open.js +0 -270
  428. package/dist/engines/pi/openai-account-models.d.ts +0 -35
  429. package/dist/engines/pi/openai-account-models.js +0 -116
  430. package/dist/engines/pi/report.d.ts +0 -18
  431. package/dist/engines/pi/report.js +0 -59
  432. package/dist/engines/pi/retry-event.d.ts +0 -6
  433. package/dist/engines/pi/retry-event.js +0 -15
  434. package/dist/engines/pi/service.d.ts +0 -9
  435. package/dist/engines/pi/service.js +0 -14
  436. package/dist/engines/pi/session-builder.d.ts +0 -13
  437. package/dist/engines/pi/session-builder.js +0 -117
  438. package/dist/engines/pi/session-control.d.ts +0 -77
  439. package/dist/engines/pi/session-control.js +0 -908
  440. package/dist/engines/pi/session-effects.d.ts +0 -14
  441. package/dist/engines/pi/session-effects.js +0 -30
  442. package/dist/engines/pi/session-inheritance.d.ts +0 -21
  443. package/dist/engines/pi/session-inheritance.js +0 -204
  444. package/dist/engines/pi/session-markers.d.ts +0 -49
  445. package/dist/engines/pi/session-markers.js +0 -60
  446. package/dist/engines/pi/session-settings.d.ts +0 -67
  447. package/dist/engines/pi/session-settings.js +0 -119
  448. package/dist/engines/pi/session-store.d.ts +0 -69
  449. package/dist/engines/pi/session-store.js +0 -430
  450. package/dist/engines/pi/tool-context.d.ts +0 -45
  451. package/dist/engines/pi/tool-context.js +0 -31
  452. package/dist/engines/pi/tool.d.ts +0 -99
  453. package/dist/engines/pi/tool.js +0 -118
  454. package/dist/engines/pi/turn-kit.d.ts +0 -92
  455. package/dist/engines/pi/turn-kit.js +0 -288
  456. package/dist/engines/pi/wake-tool.d.ts +0 -9
  457. package/dist/engines/pi/wake-tool.js +0 -92
  458. package/dist/loader.d.ts +0 -41
  459. package/dist/loader.js +0 -116
  460. package/dist/node.d.ts +0 -3
  461. package/dist/node.js +0 -4
  462. package/dist/observe.d.ts +0 -6
  463. package/dist/observe.js +0 -47
  464. package/dist/pi.d.ts +0 -25
  465. package/dist/pi.js +0 -15
  466. package/dist/scaffold/templates/APPEND_SYSTEM.md +0 -12
  467. package/dist/scaffold/templates/tools/fetch-url.ts +0 -25
  468. package/dist/schedule/discover.d.ts +0 -17
  469. package/dist/schedule/discover.js +0 -61
  470. package/dist/schedule/routine.d.ts +0 -43
  471. package/dist/schedule/routine.js +0 -32
  472. package/dist/schedule/run.d.ts +0 -55
  473. package/dist/schedule/run.js +0 -146
  474. package/dist/secrets-gate.d.ts +0 -32
  475. package/dist/secrets-gate.js +0 -33
  476. package/dist/session-remote.d.ts +0 -47
  477. package/dist/session-remote.js +0 -460
  478. package/dist/session.d.ts +0 -466
  479. package/dist/session.js +0 -57
  480. /package/dist/{channels/kit/state.d.ts → state-file.d.ts} +0 -0
@@ -0,0 +1,268 @@
1
+ /**
2
+ * RUNNING an agent's environment with mise. The agent carries its own mise, as optional npm dependencies, one package
3
+ * per platform of which npm installs only this machine's: the author installs nothing, and the image's `npm ci`
4
+ * installs the same mise. mise is given the packages as arguments (`gh@2`), and never reads a configuration file: not
5
+ * the machine's, not an author's own `mise.toml`, not a parent directory's.
6
+ *
7
+ * Nothing is locked. Each place installs the newest release a declared version allows when it first needs a package,
8
+ * and keeps it: a process that runs the agent installs only what has no install matching it yet (the image installs
9
+ * every package when it is built), and `fastagent env install` installs the newest again.
10
+ */
11
+ import { spawn } from "node:child_process";
12
+ import { createRequire } from "node:module";
13
+ import { dirname, join, resolve } from "node:path";
14
+ import { loadEnvironment } from "../content/file.js";
15
+ import { log } from "../log.js";
16
+ import { detectRuntime, listsFastagent, readPackageJson } from "../runtime.js";
17
+ import { aptAdvice, missingAptPackages } from "./apt.js";
18
+ import { missingPythonRuntime, resolvedBackendRefusal, writtenBackend, } from "./declare.js";
19
+ /** mise's npm package for each platform it publishes one for, by Node's `${platform}-${arch}`. */
20
+ const MISE_PACKAGES = {
21
+ "darwin-arm64": "@jdxcode/mise-darwin-arm64",
22
+ "darwin-x64": "@jdxcode/mise-darwin-x64",
23
+ "linux-arm64": "@jdxcode/mise-linux-arm64",
24
+ "linux-x64": "@jdxcode/mise-linux-x64",
25
+ };
26
+ /** Every platform's package: an agent lists them all, so its lockfile has the image's too. */
27
+ export const MISE_PACKAGE_NAMES = Object.values(MISE_PACKAGES);
28
+ /** A path no file can be at, for the mise configuration files an agent's environment must not read. */
29
+ const NO_FILE = "/dev/null/none.toml";
30
+ /** This machine's mise package; a platform mise publishes none for cannot run an environment. */
31
+ function platformPackage() {
32
+ const key = `${process.platform}-${process.arch}`;
33
+ const name = MISE_PACKAGES[key];
34
+ if (!name)
35
+ throw new Error(`an agent's environment does not run on ${key} yet: mise has no npm package for it`);
36
+ return name;
37
+ }
38
+ /**
39
+ * The agent's mise on this machine, or undefined when the agent has none installed. Found by its package, not by
40
+ * `node_modules/.bin/mise`: the platform packages all name that bin, and npm drops the link while it skips the
41
+ * other platforms' packages.
42
+ */
43
+ export function miseBinary(agentDir) {
44
+ const require = createRequire(join(resolve(agentDir), "package.json"));
45
+ try {
46
+ return join(dirname(require.resolve(`${platformPackage()}/package.json`)), "bin", "mise");
47
+ }
48
+ catch (error) {
49
+ if (error.code === "MODULE_NOT_FOUND")
50
+ return undefined;
51
+ throw error;
52
+ }
53
+ }
54
+ /**
55
+ * What every run of the agent's mise is given, here and in the image: no configuration file is read, so the packages
56
+ * are the arguments alone, and a runtime is installed from a prebuilt release or not at all (mise compiles python by
57
+ * default, and `all_compile`, already false, does not stop it). Given to these runs alone, never exported to the
58
+ * agent's processes: a mise the agent runs is the machine's.
59
+ */
60
+ export const MISE_ENVIRONMENT = {
61
+ MISE_GLOBAL_CONFIG_FILE: NO_FILE,
62
+ MISE_SYSTEM_CONFIG_FILE: NO_FILE,
63
+ // A path, not a name: mise looks for a file of the name given here in every directory up to the root.
64
+ MISE_OVERRIDE_CONFIG_FILENAMES: NO_FILE,
65
+ MISE_OVERRIDE_TOOL_VERSIONS_FILENAMES: NO_FILE,
66
+ MISE_PYTHON_COMPILE: "false",
67
+ MISE_NODE_COMPILE: "false",
68
+ MISE_RUBY_COMPILE: "false",
69
+ MISE_ERLANG_COMPILE: "false",
70
+ };
71
+ /** What mise is given for `packages`: each as `name@version`, here and in the image (container.ts). */
72
+ export function packageSpecs(packages) {
73
+ return Object.keys(packages).map((name) => specOf(name, packages));
74
+ }
75
+ /** How mise is given the package `name` of `packages`. */
76
+ function specOf(name, packages) {
77
+ return `${name}@${packages[name]}`;
78
+ }
79
+ /**
80
+ * Run the agent's mise with `args` in `cwd`. Its errors go to this process's stderr unless `quiet` keeps them for
81
+ * the error, which then says what mise said; `capture` returns its stdout instead of passing it through.
82
+ */
83
+ function runMise(cwd, bin, args, options = {}) {
84
+ return new Promise((done, fail) => {
85
+ const child = spawn(bin, args, {
86
+ cwd,
87
+ env: { ...process.env, ...MISE_ENVIRONMENT },
88
+ stdio: ["inherit", options.capture ? "pipe" : process.stderr, options.quiet ? "pipe" : "inherit"],
89
+ });
90
+ let stdout = "";
91
+ let stderr = "";
92
+ child.stdout?.setEncoding("utf8").on("data", (chunk) => {
93
+ stdout += chunk;
94
+ });
95
+ child.stderr?.setEncoding("utf8").on("data", (chunk) => {
96
+ stderr += chunk;
97
+ });
98
+ child.on("error", fail);
99
+ child.on("close", (code, signal) => {
100
+ if (code === 0)
101
+ return done(stdout);
102
+ const said = stderr.match(/ERROR\s+(.+)/)?.[1];
103
+ fail(new Error(`mise ${args.join(" ")} failed (${signal ?? `exit ${code}`})${said ? `: ${said}` : ""}`));
104
+ });
105
+ });
106
+ }
107
+ /**
108
+ * The command that puts mise into the agent: every platform's package, at the newest version, pinned. When
109
+ * `package.json` already lists it (a fresh clone), the dependencies are installed as they are, never upgraded.
110
+ */
111
+ async function addMiseCommand(agentDir) {
112
+ const pkg = await readPackageJson(agentDir);
113
+ const { runtime } = detectRuntime(agentDir, pkg);
114
+ if (platformPackage() in (pkg.optionalDependencies ?? {})) {
115
+ return runtime === "bun" ? ["bun", ["install"]] : ["npm", ["install", "--no-audit", "--no-fund"]];
116
+ }
117
+ const packages = MISE_PACKAGE_NAMES.map((name) => `${name}@latest`);
118
+ return runtime === "bun"
119
+ ? ["bun", ["add", "--optional", "--exact", ...packages]]
120
+ : ["npm", ["install", "--save-optional", "--save-exact", "--no-audit", "--no-fund", ...packages]];
121
+ }
122
+ /**
123
+ * The agent's mise, added to its `package.json` first when it has none: for the commands an author runs to change the
124
+ * environment (`fastagent env`), never for one that runs the agent. A manifest that does not list FastAgent is
125
+ * refused: the mise would make it a code agent, whose image runs the FastAgent its `package.json` lists.
126
+ */
127
+ export async function ensureMise(agentDir) {
128
+ const found = miseBinary(agentDir);
129
+ if (found)
130
+ return found;
131
+ if (!listsFastagent(await readPackageJson(agentDir))) {
132
+ throw new Error(`${join(agentDir, "package.json")} does not list @fastagent-sh/fastagent — the agent's mise is one of its ` +
133
+ `dependencies, and an agent with dependencies runs the FastAgent they list. Add it first: ` +
134
+ `\`npm install @fastagent-sh/fastagent\` in ${agentDir}`);
135
+ }
136
+ const [command, args] = await addMiseCommand(agentDir);
137
+ log.info(`[fastagent] installing the agent's mise: ${command} ${args.join(" ")}`);
138
+ await new Promise((done, fail) => {
139
+ const child = spawn(command, args, { cwd: agentDir, stdio: ["inherit", process.stderr, "inherit"] });
140
+ child.on("error", fail);
141
+ child.on("close", (code) => (code === 0 ? done() : fail(new Error(`${command} ${args[0]} failed (exit ${code})`))));
142
+ });
143
+ const added = miseBinary(agentDir);
144
+ if (!added)
145
+ throw new Error(`${command} ${args[0]} added mise to ${agentDir}, and it is still not installed`);
146
+ return added;
147
+ }
148
+ /**
149
+ * Check `packages` against the backend each resolves to: a written one says it (`npm:prettier`), and mise resolves a
150
+ * registry name (`tokei` is `cargo:tokei`). A name mise does not know fails the check with what mise said.
151
+ */
152
+ export async function checkPackages(agentDir, bin, packages) {
153
+ const kinds = new Map();
154
+ const refusals = [];
155
+ await Promise.all(Object.keys(packages).map(async (name) => {
156
+ const written = writtenBackend(name);
157
+ if (written)
158
+ return void kinds.set(name, written);
159
+ const out = await runMise(agentDir, bin, ["tool", name, "--json"], { capture: true, quiet: true });
160
+ const backend = JSON.parse(out).backend;
161
+ if (typeof backend !== "string")
162
+ throw new Error(`mise tool ${name} --json reported no backend`);
163
+ kinds.set(name, backend.split(":")[0] ?? backend);
164
+ const refusal = resolvedBackendRefusal(name, backend);
165
+ if (refusal)
166
+ refusals.push(refusal);
167
+ }));
168
+ const runtime = missingPythonRuntime(packages, kinds);
169
+ return { refusals: refusals.sort(), ...(runtime ? { runtime } : {}) };
170
+ }
171
+ /**
172
+ * Install `packages` here, each at the newest release its declared version allows. Those `newest` names (every one
173
+ * when it is `"all"`: `fastagent env install`) are given to mise whatever is installed, which installs a newer release
174
+ * where one is out; the rest only when nothing installed matches them, which `mise where` answers without the network,
175
+ * so a start whose packages are installed needs no network and keeps the versions it has. An install that matches may
176
+ * be another project's: mise's data directory is the machine's.
177
+ */
178
+ export async function installPackages(agentDir, bin, packages, newest = []) {
179
+ const names = Object.keys(packages);
180
+ const asked = names.filter((name) => newest === "all" || newest.includes(name));
181
+ const others = names.filter((name) => !asked.includes(name));
182
+ const installed = await Promise.all(
183
+ // A failed `where` is what mise says of a package with no install here; the install that follows reports any other
184
+ // reason it cannot be had.
185
+ others.map((name) => runMise(agentDir, bin, ["where", specOf(name, packages)], { capture: true, quiet: true }).then(() => true, () => false)));
186
+ const present = new Set(others.filter((_, i) => installed[i]));
187
+ const wanted = names.filter((name) => !present.has(name)).map((name) => specOf(name, packages));
188
+ if (wanted.length === 0)
189
+ return;
190
+ const installedSpecs = [...present].map((name) => specOf(name, packages));
191
+ // A package installs with the ones it needs (a PyPI one runs uv), which mise finds in the packages it is given or on
192
+ // the PATH. The installed ones go on the PATH: given as packages, mise would install a newer release of them.
193
+ await runInstall(agentDir, bin, wanted, installedSpecs.length > 0 ? await packagesEnvironment(agentDir, bin, installedSpecs) : {});
194
+ }
195
+ /** What `mise env` puts in a process's environment for `specs`: the PATH their commands are on. */
196
+ async function packagesEnvironment(agentDir, bin, specs) {
197
+ return JSON.parse(await runMise(agentDir, bin, ["env", "--json", ...specs], { capture: true }));
198
+ }
199
+ /**
200
+ * The notice mise prints after installing a package no configuration file names, with the `mise use` it suggests: not
201
+ * the agent's way to a package, so it is not passed on.
202
+ */
203
+ const NOT_ACTIVATED = /installed but not activated|^To install and activate, run:|^\s+mise use /;
204
+ /**
205
+ * `mise install` of `specs`, with `env` added to its environment. Its progress is passed on to this process's stderr as
206
+ * it comes, so a runtime that takes minutes is seen to; a failure carries what mise said, every line of it, since
207
+ * mise names the packages on its first line and the reason (a 404, a proxy, a rate limit) on the lines after.
208
+ */
209
+ function runInstall(agentDir, bin, specs, env) {
210
+ log.info(`[fastagent] installing the agent's packages: ${specs.join(", ")}`);
211
+ return new Promise((done, fail) => {
212
+ const child = spawn(bin, ["install", ...specs], {
213
+ cwd: agentDir,
214
+ env: { ...process.env, ...env, ...MISE_ENVIRONMENT },
215
+ stdio: ["inherit", process.stderr, "pipe"],
216
+ });
217
+ const said = [];
218
+ let partial = "";
219
+ child.stderr?.setEncoding("utf8").on("data", (chunk) => {
220
+ const lines = (partial + chunk).split("\n");
221
+ partial = lines.pop() ?? "";
222
+ for (const line of lines) {
223
+ if (NOT_ACTIVATED.test(line))
224
+ continue;
225
+ process.stderr.write(`${line}\n`);
226
+ said.push(line);
227
+ }
228
+ });
229
+ child.on("error", fail);
230
+ child.on("close", (code, signal) => {
231
+ if (partial !== "")
232
+ said.push(partial);
233
+ if (code === 0)
234
+ return done();
235
+ const reason = said
236
+ .slice(Math.max(0, said.findIndex((line) => /\bERROR\b/.test(line))))
237
+ .filter((line) => !/ERROR\s+(Version:|Run with --verbose)/.test(line))
238
+ .map((line) => line.replace(/^mise\s+ERROR\s+/, "").trim())
239
+ .filter((line) => line !== "");
240
+ fail(new Error(`mise install ${specs.join(" ")} failed (${signal ?? `exit ${code}`})${reason.length > 0 ? `: ${reason.join("; ")}` : ""}`));
241
+ });
242
+ });
243
+ }
244
+ /** What a process that runs the agent says when its environment has no mise to install it with. */
245
+ function missingMise(agentDir) {
246
+ return new Error(`context.json declares packages, and the agent has no mise installed — run \`fastagent env install\` in ${agentDir}`);
247
+ }
248
+ /**
249
+ * Enter the agent's environment, for a process that runs it: install the declared packages that have no install here
250
+ * yet, and put them first on this process's PATH, which every command the agent runs inherits. mise itself is not put
251
+ * on the PATH. An agent without packages borrows the machine's commands. Apt packages are the image's to install; one
252
+ * missing here is said, with how.
253
+ */
254
+ export async function enterEnvironment(agentDir) {
255
+ const declared = loadEnvironment(agentDir);
256
+ const missingApt = missingAptPackages(declared?.aptPackages ?? []);
257
+ if (missingApt && missingApt.length > 0)
258
+ log.warn(`[fastagent] ${aptAdvice(missingApt)}`);
259
+ const packages = declared?.packages ?? {};
260
+ if (Object.keys(packages).length === 0)
261
+ return declared;
262
+ const bin = miseBinary(agentDir);
263
+ if (!bin)
264
+ throw missingMise(agentDir);
265
+ await installPackages(agentDir, bin, packages);
266
+ Object.assign(process.env, await packagesEnvironment(agentDir, bin, packageSpecs(packages)));
267
+ return declared;
268
+ }
package/dist/feishu.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  /** `@fastagent-sh/fastagent/feishu` — the canonical Feishu (open.feishu.cn) bot-channel surface. */
2
- export { feishuChannel, feishuWebSocketChannel, defaultFeishuRoute, feishuEnvelope, type FeishuChannelOptions, type FeishuWebSocketChannelOptions, type FeishuMessageEvent, type FeishuMessage, type FeishuRoute, type FeishuFailure, } from "./channels/feishu/feishu.ts";
3
- export { feishuTransport, type FeishuTransport } from "./channels/feishu/shared-api.ts";
2
+ export { feishuChannel, feishuIngress, feishuWebSocketChannel, defaultFeishuRoute, feishuEnvelope, type FeishuChannelOptions, type FeishuWebSocketChannelOptions, type FeishuMessageEvent, type FeishuMessage, type FeishuRoute, type FeishuFailure, } from "./channels/feishu/feishu.ts";
3
+ export { feishuThreads, feishuTransport, type FeishuTransport } from "./channels/feishu/shared-api.ts";
package/dist/feishu.js CHANGED
@@ -1,3 +1,3 @@
1
1
  /** `@fastagent-sh/fastagent/feishu` — the canonical Feishu (open.feishu.cn) bot-channel surface. */
2
- export { feishuChannel, feishuWebSocketChannel, defaultFeishuRoute, feishuEnvelope, } from "./channels/feishu/feishu.js";
3
- export { feishuTransport } from "./channels/feishu/shared-api.js";
2
+ export { feishuChannel, feishuIngress, feishuWebSocketChannel, defaultFeishuRoute, feishuEnvelope, } from "./channels/feishu/feishu.js";
3
+ export { feishuThreads, feishuTransport } from "./channels/feishu/shared-api.js";
package/dist/git.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /** git is not on the PATH. Its message names what needed it: `git is not installed: <why>`. */
2
+ export declare class GitNotInstalled extends Error {
3
+ }
4
+ /** git for one purpose, `why`: what a missing git is reported as needed for. */
5
+ export declare function gitFor(why: string): {
6
+ /**
7
+ * git's answer in `cwd` (which must exist), or undefined when it answers with a non-zero exit. A git that cannot
8
+ * run is an error.
9
+ */
10
+ answer(args: string[], cwd: string): string | undefined;
11
+ /** Run git in `cwd`: its output, or its stderr as the error. */
12
+ run(args: string[], cwd: string): Promise<string>;
13
+ };
package/dist/git.js ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Running git, and what ONE result means: an answer, a "no" (a non-zero exit: not a checkout, no such ref, not
3
+ * ignored), or a git that cannot run at all. Every caller reads git through here, so a new kind of result is decided
4
+ * once; each caller says only why it needed git, which is what a missing git is reported with.
5
+ */
6
+ import { execFile, execFileSync } from "node:child_process";
7
+ import { promisify } from "node:util";
8
+ /** git is not on the PATH. Its message names what needed it: `git is not installed: <why>`. */
9
+ export class GitNotInstalled extends Error {
10
+ }
11
+ /**
12
+ * Never wait on a terminal prompt for a credential: a serving process has nobody to answer it, so git fails and says
13
+ * why instead. Not covered by a test: it takes a remote that asks for a credential.
14
+ */
15
+ const gitEnv = () => ({ ...process.env, GIT_TERMINAL_PROMPT: "0" });
16
+ const execGit = promisify(execFile);
17
+ /** git for one purpose, `why`: what a missing git is reported as needed for. */
18
+ export function gitFor(why) {
19
+ const missing = () => new GitNotInstalled(`git is not installed: ${why}`);
20
+ return {
21
+ /**
22
+ * git's answer in `cwd` (which must exist), or undefined when it answers with a non-zero exit. A git that cannot
23
+ * run is an error.
24
+ */
25
+ answer(args, cwd) {
26
+ try {
27
+ return execFileSync("git", args, {
28
+ cwd,
29
+ env: gitEnv(),
30
+ encoding: "utf8",
31
+ stdio: ["ignore", "pipe", "pipe"],
32
+ }).trim();
33
+ }
34
+ catch (error) {
35
+ if (error.code === "ENOENT")
36
+ throw missing();
37
+ if (typeof error.status === "number")
38
+ return undefined;
39
+ throw error;
40
+ }
41
+ },
42
+ /** Run git in `cwd`: its output, or its stderr as the error. */
43
+ async run(args, cwd) {
44
+ try {
45
+ return (await execGit("git", args, { cwd, env: gitEnv() })).stdout;
46
+ }
47
+ catch (error) {
48
+ if (error.code === "ENOENT")
49
+ throw missing();
50
+ const stderr = String(error.stderr ?? "").trim();
51
+ throw new Error(stderr || error.message);
52
+ }
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,20 @@
1
+ import { type Models, type ModelsStore, type Provider } from "@earendil-works/pi-ai";
2
+ export interface Catalogs {
3
+ /** A built-in provider, listing what the published catalog adds to it; one that fetches its own list, as it is. */
4
+ over(provider: Provider): Provider;
5
+ /** Where pi-ai keeps the list of a provider that fetches its own. */
6
+ fetched: ModelsStore;
7
+ /**
8
+ * Bring the lists of `models` (built over {@link over} and {@link fetched}) up to date: the kept ones, then, unless
9
+ * {@link OFFLINE_VARIABLE} is set, what the network has newer. Never rejects: resolves with why a list is not
10
+ * current, when one is not.
11
+ */
12
+ refresh(models: Models, options?: {
13
+ force?: boolean;
14
+ }): Promise<string | undefined>;
15
+ }
16
+ /** The catalogs kept in `dir`, or, without one, for this process alone. `url` and `timeoutMs` are for tests. */
17
+ export declare function catalogsIn(dir: string | undefined, options?: {
18
+ url?: string;
19
+ timeoutMs?: number;
20
+ }): Catalogs;
@@ -0,0 +1,254 @@
1
+ /**
2
+ * The model lists a pi-ai release does not carry yet. pi publishes the catalog of every built-in provider at pi.dev;
3
+ * when it is newer than the data this pi-ai was built with, each built-in provider lists its models too, so a model a
4
+ * provider releases can be named without a FastAgent release. A provider that fetches its own list (radius, a provider
5
+ * in code with `fetchModels`) is refreshed by pi-ai, with the credential that needs. Both are kept in the instance's
6
+ * state, so a start without the network, or whose refresh fails, has the lists it last had.
7
+ *
8
+ * Adapted from pi-coding-agent's `remote-catalog-provider` (MIT). Two differences: one request answers for every
9
+ * provider, and the published catalog is fetched whatever this machine authenticates (pi-ai's refresh asks the network
10
+ * only for a provider holding a credential), because a deployed box opens its agent, default model checked, before
11
+ * anyone logs in on it.
12
+ */
13
+ import { readFileSync, rmSync } from "node:fs";
14
+ import { dirname, join } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+ import { InMemoryModelsStore, getModelType, isModelType, } from "@earendil-works/pi-ai";
17
+ import { getBuiltinModelDataGeneratedAt } from "@earendil-works/pi-ai/providers/all";
18
+ import { writeFileAtomic } from "../../atomic-write.js";
19
+ const PUBLISHED_URL = "https://pi.dev/api/models";
20
+ /** The model types this pi-ai takes; asked for, since pi.dev answers a client that names none with chat models only. */
21
+ const MODEL_TYPES = ["chat", "image", "classifier"];
22
+ /** How long a fetched catalog counts as current, so a start inside it asks the network nothing. pi's own. */
23
+ const FRESH_MS = 4 * 60 * 60 * 1000;
24
+ const TIMEOUT_MS = 5_000;
25
+ /** `1`: no model list is fetched, and each is the one last kept. */
26
+ const OFFLINE_VARIABLE = "FASTAGENT_OFFLINE";
27
+ /** The catalogs kept in `dir`, or, without one, for this process alone. `url` and `timeoutMs` are for tests. */
28
+ export function catalogsIn(dir, options = {}) {
29
+ const builtAt = getBuiltinModelDataGeneratedAt();
30
+ const timeoutMs = options.timeoutMs ?? TIMEOUT_MS;
31
+ const fetched = dir === undefined ? new InMemoryModelsStore() : providerFiles(join(dir, "providers"));
32
+ const publishedPath = dir === undefined ? undefined : join(dir, "published.json");
33
+ let inMemory;
34
+ let listed = {};
35
+ const show = (kept) => {
36
+ listed = kept && (builtAt === undefined || kept.lastModified > builtAt) ? kept.models : {};
37
+ };
38
+ const keep = (published) => {
39
+ if (publishedPath === undefined)
40
+ inMemory = published;
41
+ // Shared writers: a serve and an `invoke` may refresh at once, and either whole catalog is a good one.
42
+ else
43
+ writeFileAtomic(publishedPath, JSON.stringify(published), undefined, true);
44
+ };
45
+ async function refreshPublished(network, force, signal) {
46
+ const piAi = piAiVersion();
47
+ const found = publishedPath === undefined ? inMemory : readJson(publishedPath);
48
+ const kept = found?.piAi === piAi ? found : undefined;
49
+ show(kept);
50
+ if (!network || (!force && kept && Date.now() - kept.checkedAt < FRESH_MS))
51
+ return;
52
+ try {
53
+ await askPublished(piAi, kept, signal);
54
+ }
55
+ catch (error) {
56
+ // A failed ask counts as one: the catalog kept stays, and is not asked for again for FRESH_MS. With none kept,
57
+ // the next open asks, since a default model may need it.
58
+ if (kept)
59
+ keep({ ...kept, checkedAt: Date.now() });
60
+ throw error;
61
+ }
62
+ }
63
+ async function askPublished(piAi, kept, signal) {
64
+ const url = new URL(options.url ?? PUBLISHED_URL);
65
+ url.searchParams.set("types", MODEL_TYPES.join(","));
66
+ url.searchParams.set("pi-version", piAi);
67
+ const validator = kept?.etag;
68
+ const response = await fetch(url, {
69
+ headers: { accept: "application/json", ...(validator === undefined ? {} : { "if-none-match": validator }) },
70
+ signal,
71
+ });
72
+ if (response.status === 304 && kept) {
73
+ keep({ ...kept, checkedAt: Date.now() });
74
+ return;
75
+ }
76
+ if (response.status === 404 || response.status === 501) {
77
+ // pi.dev publishes no catalog for this pi-ai, so its own lists are the current ones.
78
+ const none = { piAi, models: {}, lastModified: 0, checkedAt: Date.now() };
79
+ keep(none);
80
+ show(none);
81
+ return;
82
+ }
83
+ if (!response.ok)
84
+ throw new Error(`${url.origin} answered ${response.status}`);
85
+ const needs = response.headers.get("x-pi-model-catalog-minimum-version");
86
+ if (needs !== null && olderThan(piAi, needs)) {
87
+ throw new Error(`${url.origin} answered a catalog for pi-ai ${needs} or later, and this is ${piAi}`);
88
+ }
89
+ const lastModified = Date.parse(response.headers.get("last-modified") ?? "");
90
+ const etag = response.headers.get("etag");
91
+ const published = {
92
+ piAi,
93
+ models: parseCatalog(await response.json()),
94
+ lastModified: Number.isNaN(lastModified) ? 0 : lastModified,
95
+ ...(etag === null ? {} : { etag }),
96
+ checkedAt: Date.now(),
97
+ };
98
+ keep(published);
99
+ show(published);
100
+ }
101
+ return {
102
+ over(provider) {
103
+ if (provider.refreshModels !== undefined)
104
+ return provider;
105
+ const added = () => listed[provider.id] ?? [];
106
+ return {
107
+ ...provider,
108
+ getModels: () => merged(provider.getModels(), added().filter((model) => isModelType(model, "chat"))),
109
+ getAllModels: () => merged(provider.getAllModels?.() ?? provider.getModels(), added()),
110
+ };
111
+ },
112
+ fetched,
113
+ async refresh(models, { force = false } = {}) {
114
+ const offline = process.env[OFFLINE_VARIABLE] === "1";
115
+ const signal = AbortSignal.timeout(timeoutMs);
116
+ const why = (error) => signal.aborted ? `no answer within ${timeoutMs / 1000}s` : messageOf(error);
117
+ const [published, own] = await Promise.all([
118
+ refreshPublished(!offline, force, signal).then(() => undefined, (error) => `pi's published catalog: ${why(error)}`),
119
+ refreshOwn(models, !offline, force, signal),
120
+ ]);
121
+ const problems = [
122
+ ...(offline && force ? [`${OFFLINE_VARIABLE}=1 is set`] : []),
123
+ ...(published === undefined ? [] : [published]),
124
+ ...[...own.errors].map(([provider, error]) => `${provider}: ${messageOf(error)}`),
125
+ ...(own.aborted ? [`the providers that fetch their own: ${why(undefined)}`] : []),
126
+ ];
127
+ if (problems.length === 0)
128
+ return undefined;
129
+ const skip = offline ? "" : `; ${OFFLINE_VARIABLE}=1 skips the refresh`;
130
+ return `model lists not refreshed (${problems.join("; ")}): each lists what it last had${skip}`;
131
+ },
132
+ };
133
+ /**
134
+ * The providers that fetch their own list, each asked only once what it last fetched is {@link FRESH_MS} old: pi-ai
135
+ * asks every one of them each refresh it allows the network, and keeps when it last did beside the list.
136
+ */
137
+ async function refreshOwn(models, network, force, signal) {
138
+ // An entry it cannot read counts as none: pi-ai's refresh reads it again, and reports why it could not.
139
+ const keptOf = (id) => fetched.read(id).then((entry) => entry, () => undefined);
140
+ const own = models.getProviders().filter((provider) => provider.refreshModels !== undefined);
141
+ const stale = new Map();
142
+ const fresh = [];
143
+ for (const { id } of own) {
144
+ const kept = await keptOf(id);
145
+ const checkedAt = kept?.checkedAt;
146
+ if (force || checkedAt === undefined || Date.now() - checkedAt >= FRESH_MS)
147
+ stale.set(id, kept);
148
+ else
149
+ fresh.push(id);
150
+ }
151
+ const [restored, asked] = await Promise.all([
152
+ models.refresh({ allowNetwork: false, providers: fresh, signal }),
153
+ models.refresh({ allowNetwork: network, providers: [...stale.keys()], force, signal }),
154
+ ]);
155
+ // A failed ask counts as one, as for the published catalog: pi-ai replaces an entry only when it fetched a list, so
156
+ // one it left as it was is a list asked for and kept, not asked for again for FRESH_MS.
157
+ const unwritten = new Map();
158
+ for (const [id, kept] of network ? stale : []) {
159
+ if (kept === undefined || (await keptOf(id))?.checkedAt !== kept.checkedAt)
160
+ continue;
161
+ await fetched
162
+ .write(id, { ...kept, checkedAt: Date.now() })
163
+ .catch((error) => void unwritten.set(id, error));
164
+ }
165
+ return {
166
+ aborted: restored.aborted || asked.aborted,
167
+ errors: new Map([...restored.errors, ...asked.errors, ...unwritten]),
168
+ };
169
+ }
170
+ }
171
+ /** The pi-ai this process runs, from its own package.json, which its exports do not reach. */
172
+ function piAiVersion() {
173
+ let dir = dirname(fileURLToPath(import.meta.resolve("@earendil-works/pi-ai")));
174
+ for (;;) {
175
+ const pkg = readJson(join(dir, "package.json"));
176
+ if (pkg?.name === "@earendil-works/pi-ai" && typeof pkg.version === "string")
177
+ return pkg.version;
178
+ const parent = dirname(dir);
179
+ if (parent === dir)
180
+ throw new Error("no package.json of @earendil-works/pi-ai above its entry");
181
+ dir = parent;
182
+ }
183
+ }
184
+ /** Whether release `version` is older than `than`, by `major.minor.patch`. */
185
+ function olderThan(version, than) {
186
+ const parts = (release) => {
187
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(release);
188
+ if (match === null)
189
+ throw new Error(`"${release}" is not a version`);
190
+ return match.slice(1).map(Number);
191
+ };
192
+ const [ours, theirs] = [parts(version), parts(than)];
193
+ for (const [index, part] of ours.entries()) {
194
+ const other = theirs[index];
195
+ if (part !== other)
196
+ return part < other;
197
+ }
198
+ return false;
199
+ }
200
+ /** Published models override built-in ones of the same type and id, as in pi. */
201
+ function merged(builtIn, published) {
202
+ const byKey = new Map();
203
+ for (const model of [...builtIn, ...published])
204
+ byKey.set(`${getModelType(model)}\0${model.id}`, model);
205
+ return [...byKey.values()];
206
+ }
207
+ /** pi.dev's answer: each provider's models, as a list (with `types`) or keyed by id (without). */
208
+ function parseCatalog(value) {
209
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
210
+ throw new Error("the answer is not a model catalog");
211
+ }
212
+ const catalog = {};
213
+ for (const [provider, models] of Object.entries(value)) {
214
+ const entries = Array.isArray(models) ? models : typeof models === "object" ? Object.values(models) : [];
215
+ catalog[provider] = entries
216
+ .filter((entry) => typeof entry === "object" &&
217
+ entry !== null &&
218
+ typeof entry.id === "string" &&
219
+ [undefined, ...MODEL_TYPES].includes(entry.type))
220
+ .map((model) => ({ ...model, provider }));
221
+ }
222
+ return catalog;
223
+ }
224
+ /** One file per provider: each is written by its own refresh, so none waits on another's lock. */
225
+ function providerFiles(dir) {
226
+ const path = (providerId) => join(dir, `${encodeURIComponent(providerId)}.json`);
227
+ return {
228
+ read: async (providerId) => readJson(path(providerId)),
229
+ write: async (providerId, entry) => writeFileAtomic(path(providerId), JSON.stringify(entry), undefined, true),
230
+ delete: async (providerId) => rmSync(path(providerId), { force: true }),
231
+ };
232
+ }
233
+ function readJson(path) {
234
+ let text;
235
+ try {
236
+ text = readFileSync(path, "utf8");
237
+ }
238
+ catch (error) {
239
+ if (error.code === "ENOENT")
240
+ return undefined;
241
+ throw error;
242
+ }
243
+ try {
244
+ return JSON.parse(text);
245
+ }
246
+ catch {
247
+ throw new Error(`${path} is not JSON: delete it, and the next refresh fetches it again`);
248
+ }
249
+ }
250
+ /** pi-ai and `fetch` put the reason in the cause ("fetch failed", "Model refresh failed for radius"). */
251
+ function messageOf(error) {
252
+ const cause = error.cause instanceof Error ? `: ${error.cause.message}` : "";
253
+ return `${error.message}${cause}`;
254
+ }
@@ -11,8 +11,8 @@ export interface FastagentAuthOptions {
11
11
  /**
12
12
  * Where a caller's model credentials live: fastagent's JSON file (`authPath`, or the default layers when neither is
13
13
  * given), or a {@link CredentialStore} the caller owns (an OS keychain, Electron `safeStorage`). A supplied store
14
- * replaces the FILES: none is read or written, and no global layer applies. Env variables and `models.json` keys still
15
- * apply, below a stored credential, exactly as with `authPath`. The store owns what the file store does
14
+ * replaces the FILES: none is read or written, and no global layer applies. Env variables and a provider's own auth
15
+ * still apply, below a stored credential, exactly as with `authPath`. The store owns what the file store does
16
16
  * for its file: `modify` for one provider must run one at a time, because an OAuth refresh happens inside it and a
17
17
  * rotated refresh token must never be used twice; and a store it cannot parse must not be answered as empty, or the
18
18
  * next write replaces every provider's credential.
@@ -45,7 +45,7 @@ export type FastagentCredentialStore = CredentialStore & {
45
45
  * to avoid. A provider present in neither layer is new, and new credentials belong to the primary.
46
46
  *
47
47
  * The fallback also yields to `projectAuthenticates`: a provider the project authenticates some other way (an env key,
48
- * its own models.json) never reaches it — for reading, listing or writing back.
48
+ * a provider in its config that authenticates itself) never reaches it — for reading, listing or writing back.
49
49
  */
50
50
  export declare function fastagentCredentialStore(path?: string, options?: FastagentAuthOptions & {
51
51
  fallbackPath?: string;