@xyit/wellspring 0.0.0-stage → 1.0.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 (459) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +829 -2
  3. package/dist/agent/pending-asks.d.ts +20 -0
  4. package/dist/agent/pending-asks.js +70 -0
  5. package/dist/agent/pending-asks.js.map +1 -0
  6. package/dist/agent/task-registry.d.ts +14 -0
  7. package/dist/agent/task-registry.js +80 -0
  8. package/dist/agent/task-registry.js.map +1 -0
  9. package/dist/agent/tool-context.d.ts +30 -0
  10. package/dist/agent/tool-context.js +10 -0
  11. package/dist/agent/tool-context.js.map +1 -0
  12. package/dist/agent/usage.d.ts +40 -0
  13. package/dist/agent/usage.js +122 -0
  14. package/dist/agent/usage.js.map +1 -0
  15. package/dist/api/host/agent.d.ts +165 -0
  16. package/dist/api/host/agent.js +5 -0
  17. package/dist/api/host/agent.js.map +1 -0
  18. package/dist/api/host/events.d.ts +2 -0
  19. package/dist/api/host/events.js +6 -0
  20. package/dist/api/host/events.js.map +1 -0
  21. package/dist/api/host/exec.d.ts +36 -0
  22. package/dist/api/host/exec.js +10 -0
  23. package/dist/api/host/exec.js.map +1 -0
  24. package/dist/api/host/fs.d.ts +24 -0
  25. package/dist/api/host/fs.js +7 -0
  26. package/dist/api/host/fs.js.map +1 -0
  27. package/dist/api/host/hooks.d.ts +210 -0
  28. package/dist/api/host/hooks.js +401 -0
  29. package/dist/api/host/hooks.js.map +1 -0
  30. package/dist/api/host/host.d.ts +36 -0
  31. package/dist/api/host/host.js +53 -0
  32. package/dist/api/host/host.js.map +1 -0
  33. package/dist/api/host/http.d.ts +58 -0
  34. package/dist/api/host/http.js +6 -0
  35. package/dist/api/host/http.js.map +1 -0
  36. package/dist/api/host/index.d.ts +15 -0
  37. package/dist/api/host/index.js +20 -0
  38. package/dist/api/host/index.js.map +1 -0
  39. package/dist/api/host/models.d.ts +65 -0
  40. package/dist/api/host/models.js +8 -0
  41. package/dist/api/host/models.js.map +1 -0
  42. package/dist/api/host/network.d.ts +22 -0
  43. package/dist/api/host/network.js +7 -0
  44. package/dist/api/host/network.js.map +1 -0
  45. package/dist/api/host/plugin.d.ts +138 -0
  46. package/dist/api/host/plugin.js +6 -0
  47. package/dist/api/host/plugin.js.map +1 -0
  48. package/dist/api/host/plugins.d.ts +45 -0
  49. package/dist/api/host/plugins.js +6 -0
  50. package/dist/api/host/plugins.js.map +1 -0
  51. package/dist/api/host/pty.d.ts +55 -0
  52. package/dist/api/host/pty.js +21 -0
  53. package/dist/api/host/pty.js.map +1 -0
  54. package/dist/api/host/session.d.ts +5 -0
  55. package/dist/api/host/session.js +52 -0
  56. package/dist/api/host/session.js.map +1 -0
  57. package/dist/api/host/version.d.ts +2 -0
  58. package/dist/api/host/version.js +3 -0
  59. package/dist/api/host/version.js.map +1 -0
  60. package/dist/api/host/workspace.d.ts +38 -0
  61. package/dist/api/host/workspace.js +9 -0
  62. package/dist/api/host/workspace.js.map +1 -0
  63. package/dist/api/plugin/http-client.d.ts +42 -0
  64. package/dist/api/plugin/http-client.js +364 -0
  65. package/dist/api/plugin/http-client.js.map +1 -0
  66. package/dist/api/plugin/http-protocol.d.ts +152 -0
  67. package/dist/api/plugin/http-protocol.js +49 -0
  68. package/dist/api/plugin/http-protocol.js.map +1 -0
  69. package/dist/api/result/result.d.ts +16 -0
  70. package/dist/api/result/result.js +43 -0
  71. package/dist/api/result/result.js.map +1 -0
  72. package/dist/commands/agent-turn.d.ts +63 -0
  73. package/dist/commands/agent-turn.js +687 -0
  74. package/dist/commands/agent-turn.js.map +1 -0
  75. package/dist/commands/ask-prompt.d.ts +6 -0
  76. package/dist/commands/ask-prompt.js +49 -0
  77. package/dist/commands/ask-prompt.js.map +1 -0
  78. package/dist/commands/config.d.ts +2 -0
  79. package/dist/commands/config.js +283 -0
  80. package/dist/commands/config.js.map +1 -0
  81. package/dist/commands/conversation.d.ts +51 -0
  82. package/dist/commands/conversation.js +630 -0
  83. package/dist/commands/conversation.js.map +1 -0
  84. package/dist/commands/doctor.d.ts +2 -0
  85. package/dist/commands/doctor.js +77 -0
  86. package/dist/commands/doctor.js.map +1 -0
  87. package/dist/commands/exec-approval.d.ts +3 -0
  88. package/dist/commands/exec-approval.js +80 -0
  89. package/dist/commands/exec-approval.js.map +1 -0
  90. package/dist/commands/help.d.ts +2 -0
  91. package/dist/commands/help.js +35 -0
  92. package/dist/commands/help.js.map +1 -0
  93. package/dist/commands/key.d.ts +2 -0
  94. package/dist/commands/key.js +226 -0
  95. package/dist/commands/key.js.map +1 -0
  96. package/dist/commands/plugin.d.ts +2 -0
  97. package/dist/commands/plugin.js +188 -0
  98. package/dist/commands/plugin.js.map +1 -0
  99. package/dist/commands/session.d.ts +2 -0
  100. package/dist/commands/session.js +167 -0
  101. package/dist/commands/session.js.map +1 -0
  102. package/dist/commands/shared.d.ts +35 -0
  103. package/dist/commands/shared.js +66 -0
  104. package/dist/commands/shared.js.map +1 -0
  105. package/dist/commands/start.d.ts +2 -0
  106. package/dist/commands/start.js +75 -0
  107. package/dist/commands/start.js.map +1 -0
  108. package/dist/commands/stop.d.ts +2 -0
  109. package/dist/commands/stop.js +24 -0
  110. package/dist/commands/stop.js.map +1 -0
  111. package/dist/commands/version.d.ts +2 -0
  112. package/dist/commands/version.js +33 -0
  113. package/dist/commands/version.js.map +1 -0
  114. package/dist/config/codes.d.ts +13 -0
  115. package/dist/config/codes.js +15 -0
  116. package/dist/config/codes.js.map +1 -0
  117. package/dist/config/credentials.d.ts +67 -0
  118. package/dist/config/credentials.js +214 -0
  119. package/dist/config/credentials.js.map +1 -0
  120. package/dist/config/defaults.d.ts +5 -0
  121. package/dist/config/defaults.js +31 -0
  122. package/dist/config/defaults.js.map +1 -0
  123. package/dist/config/index.d.ts +5 -0
  124. package/dist/config/index.js +10 -0
  125. package/dist/config/index.js.map +1 -0
  126. package/dist/config/loader.d.ts +10 -0
  127. package/dist/config/loader.js +68 -0
  128. package/dist/config/loader.js.map +1 -0
  129. package/dist/config/providers.d.ts +30 -0
  130. package/dist/config/providers.js +97 -0
  131. package/dist/config/providers.js.map +1 -0
  132. package/dist/config/types.d.ts +75 -0
  133. package/dist/config/types.js +4 -0
  134. package/dist/config/types.js.map +1 -0
  135. package/dist/config/validate.d.ts +4 -0
  136. package/dist/config/validate.js +391 -0
  137. package/dist/config/validate.js.map +1 -0
  138. package/dist/display/markdown.d.ts +7 -0
  139. package/dist/display/markdown.js +116 -0
  140. package/dist/display/markdown.js.map +1 -0
  141. package/dist/display/tool-label.d.ts +6 -0
  142. package/dist/display/tool-label.js +81 -0
  143. package/dist/display/tool-label.js.map +1 -0
  144. package/dist/event/event.d.ts +27 -0
  145. package/dist/event/event.js +152 -0
  146. package/dist/event/event.js.map +1 -0
  147. package/dist/index.d.ts +2 -0
  148. package/dist/index.js +308 -0
  149. package/dist/index.js.map +1 -0
  150. package/dist/input/codes.d.ts +15 -0
  151. package/dist/input/codes.js +20 -0
  152. package/dist/input/codes.js.map +1 -0
  153. package/dist/input/commands.d.ts +10 -0
  154. package/dist/input/commands.js +65 -0
  155. package/dist/input/commands.js.map +1 -0
  156. package/dist/input/help.d.ts +8 -0
  157. package/dist/input/help.js +51 -0
  158. package/dist/input/help.js.map +1 -0
  159. package/dist/input/index.d.ts +6 -0
  160. package/dist/input/index.js +10 -0
  161. package/dist/input/index.js.map +1 -0
  162. package/dist/input/parse-options.d.ts +11 -0
  163. package/dist/input/parse-options.js +51 -0
  164. package/dist/input/parse-options.js.map +1 -0
  165. package/dist/input/parse.d.ts +3 -0
  166. package/dist/input/parse.js +119 -0
  167. package/dist/input/parse.js.map +1 -0
  168. package/dist/input/types.d.ts +17 -0
  169. package/dist/input/types.js +2 -0
  170. package/dist/input/types.js.map +1 -0
  171. package/dist/logger/elog4js.d.ts +65 -0
  172. package/dist/logger/elog4js.js +305 -0
  173. package/dist/logger/elog4js.js.map +1 -0
  174. package/dist/logger/logger.d.ts +37 -0
  175. package/dist/logger/logger.js +196 -0
  176. package/dist/logger/logger.js.map +1 -0
  177. package/dist/network/index.d.ts +2 -0
  178. package/dist/network/index.js +6 -0
  179. package/dist/network/index.js.map +1 -0
  180. package/dist/network/openai.d.ts +117 -0
  181. package/dist/network/openai.js +443 -0
  182. package/dist/network/openai.js.map +1 -0
  183. package/dist/network/proxy.d.ts +9 -0
  184. package/dist/network/proxy.js +72 -0
  185. package/dist/network/proxy.js.map +1 -0
  186. package/dist/network/web.d.ts +34 -0
  187. package/dist/network/web.js +152 -0
  188. package/dist/network/web.js.map +1 -0
  189. package/dist/platform/clipboard.d.ts +15 -0
  190. package/dist/platform/clipboard.js +82 -0
  191. package/dist/platform/clipboard.js.map +1 -0
  192. package/dist/platform/exec.d.ts +28 -0
  193. package/dist/platform/exec.js +95 -0
  194. package/dist/platform/exec.js.map +1 -0
  195. package/dist/platform/file.d.ts +38 -0
  196. package/dist/platform/file.js +197 -0
  197. package/dist/platform/file.js.map +1 -0
  198. package/dist/platform/index.d.ts +7 -0
  199. package/dist/platform/index.js +12 -0
  200. package/dist/platform/index.js.map +1 -0
  201. package/dist/platform/notification.d.ts +14 -0
  202. package/dist/platform/notification.js +82 -0
  203. package/dist/platform/notification.js.map +1 -0
  204. package/dist/platform/open-external.d.ts +10 -0
  205. package/dist/platform/open-external.js +105 -0
  206. package/dist/platform/open-external.js.map +1 -0
  207. package/dist/platform/os.d.ts +20 -0
  208. package/dist/platform/os.js +86 -0
  209. package/dist/platform/os.js.map +1 -0
  210. package/dist/platform/path.d.ts +32 -0
  211. package/dist/platform/path.js +154 -0
  212. package/dist/platform/path.js.map +1 -0
  213. package/dist/plugin/access-guard.d.ts +1 -0
  214. package/dist/plugin/access-guard.js +40 -0
  215. package/dist/plugin/access-guard.js.map +1 -0
  216. package/dist/plugin/agent-service.d.ts +25 -0
  217. package/dist/plugin/agent-service.js +809 -0
  218. package/dist/plugin/agent-service.js.map +1 -0
  219. package/dist/plugin/agent-tools.d.ts +12 -0
  220. package/dist/plugin/agent-tools.js +31 -0
  221. package/dist/plugin/agent-tools.js.map +1 -0
  222. package/dist/plugin/capabilities.d.ts +26 -0
  223. package/dist/plugin/capabilities.js +192 -0
  224. package/dist/plugin/capabilities.js.map +1 -0
  225. package/dist/plugin/console-guard.d.ts +2 -0
  226. package/dist/plugin/console-guard.js +52 -0
  227. package/dist/plugin/console-guard.js.map +1 -0
  228. package/dist/plugin/data-access.d.ts +15 -0
  229. package/dist/plugin/data-access.js +57 -0
  230. package/dist/plugin/data-access.js.map +1 -0
  231. package/dist/plugin/fs.d.ts +3 -0
  232. package/dist/plugin/fs.js +93 -0
  233. package/dist/plugin/fs.js.map +1 -0
  234. package/dist/plugin/host-alias.d.ts +2 -0
  235. package/dist/plugin/host-alias.js +57 -0
  236. package/dist/plugin/host-alias.js.map +1 -0
  237. package/dist/plugin/index.d.ts +7 -0
  238. package/dist/plugin/index.js +11 -0
  239. package/dist/plugin/index.js.map +1 -0
  240. package/dist/plugin/install.d.ts +12 -0
  241. package/dist/plugin/install.js +114 -0
  242. package/dist/plugin/install.js.map +1 -0
  243. package/dist/plugin/loader.d.ts +45 -0
  244. package/dist/plugin/loader.js +739 -0
  245. package/dist/plugin/loader.js.map +1 -0
  246. package/dist/plugin/manage.d.ts +31 -0
  247. package/dist/plugin/manage.js +265 -0
  248. package/dist/plugin/manage.js.map +1 -0
  249. package/dist/plugin/models-service.d.ts +15 -0
  250. package/dist/plugin/models-service.js +317 -0
  251. package/dist/plugin/models-service.js.map +1 -0
  252. package/dist/plugin/network-service.d.ts +10 -0
  253. package/dist/plugin/network-service.js +87 -0
  254. package/dist/plugin/network-service.js.map +1 -0
  255. package/dist/plugin/paths.d.ts +6 -0
  256. package/dist/plugin/paths.js +15 -0
  257. package/dist/plugin/paths.js.map +1 -0
  258. package/dist/plugin/registry.d.ts +17 -0
  259. package/dist/plugin/registry.js +82 -0
  260. package/dist/plugin/registry.js.map +1 -0
  261. package/dist/plugin/runtime.d.ts +9 -0
  262. package/dist/plugin/runtime.js +43 -0
  263. package/dist/plugin/runtime.js.map +1 -0
  264. package/dist/plugin/scaffold.d.ts +13 -0
  265. package/dist/plugin/scaffold.js +126 -0
  266. package/dist/plugin/scaffold.js.map +1 -0
  267. package/dist/plugin/state.d.ts +6 -0
  268. package/dist/plugin/state.js +38 -0
  269. package/dist/plugin/state.js.map +1 -0
  270. package/dist/plugin/zip.d.ts +7 -0
  271. package/dist/plugin/zip.js +138 -0
  272. package/dist/plugin/zip.js.map +1 -0
  273. package/dist/sandbox/audit.d.ts +12 -0
  274. package/dist/sandbox/audit.js +20 -0
  275. package/dist/sandbox/audit.js.map +1 -0
  276. package/dist/sandbox/backends/bwrap.d.ts +28 -0
  277. package/dist/sandbox/backends/bwrap.js +197 -0
  278. package/dist/sandbox/backends/bwrap.js.map +1 -0
  279. package/dist/sandbox/backends/direct.d.ts +7 -0
  280. package/dist/sandbox/backends/direct.js +32 -0
  281. package/dist/sandbox/backends/direct.js.map +1 -0
  282. package/dist/sandbox/bridge-state.d.ts +13 -0
  283. package/dist/sandbox/bridge-state.js +12 -0
  284. package/dist/sandbox/bridge-state.js.map +1 -0
  285. package/dist/sandbox/index.d.ts +5 -0
  286. package/dist/sandbox/index.js +6 -0
  287. package/dist/sandbox/index.js.map +1 -0
  288. package/dist/sandbox/manager.d.ts +8 -0
  289. package/dist/sandbox/manager.js +325 -0
  290. package/dist/sandbox/manager.js.map +1 -0
  291. package/dist/sandbox/memory-limit.d.ts +44 -0
  292. package/dist/sandbox/memory-limit.js +193 -0
  293. package/dist/sandbox/memory-limit.js.map +1 -0
  294. package/dist/sandbox/plugin-client.d.ts +80 -0
  295. package/dist/sandbox/plugin-client.js +1601 -0
  296. package/dist/sandbox/plugin-client.js.map +1 -0
  297. package/dist/sandbox/plugin-host.d.ts +1 -0
  298. package/dist/sandbox/plugin-host.js +1001 -0
  299. package/dist/sandbox/plugin-host.js.map +1 -0
  300. package/dist/sandbox/protocol.d.ts +43 -0
  301. package/dist/sandbox/protocol.js +96 -0
  302. package/dist/sandbox/protocol.js.map +1 -0
  303. package/dist/sandbox/rpc-timeouts.d.ts +28 -0
  304. package/dist/sandbox/rpc-timeouts.js +15 -0
  305. package/dist/sandbox/rpc-timeouts.js.map +1 -0
  306. package/dist/sandbox/supervisor.d.ts +31 -0
  307. package/dist/sandbox/supervisor.js +114 -0
  308. package/dist/sandbox/supervisor.js.map +1 -0
  309. package/dist/sandbox/types.d.ts +124 -0
  310. package/dist/sandbox/types.js +17 -0
  311. package/dist/sandbox/types.js.map +1 -0
  312. package/dist/service/control.d.ts +32 -0
  313. package/dist/service/control.js +117 -0
  314. package/dist/service/control.js.map +1 -0
  315. package/dist/session/compress.d.ts +3 -0
  316. package/dist/session/compress.js +42 -0
  317. package/dist/session/compress.js.map +1 -0
  318. package/dist/session/index.d.ts +4 -0
  319. package/dist/session/index.js +8 -0
  320. package/dist/session/index.js.map +1 -0
  321. package/dist/session/registry.d.ts +4 -0
  322. package/dist/session/registry.js +12 -0
  323. package/dist/session/registry.js.map +1 -0
  324. package/dist/session/replay.d.ts +10 -0
  325. package/dist/session/replay.js +109 -0
  326. package/dist/session/replay.js.map +1 -0
  327. package/dist/session/session.d.ts +40 -0
  328. package/dist/session/session.js +108 -0
  329. package/dist/session/session.js.map +1 -0
  330. package/dist/session/store.d.ts +58 -0
  331. package/dist/session/store.js +382 -0
  332. package/dist/session/store.js.map +1 -0
  333. package/dist/session/types.d.ts +56 -0
  334. package/dist/session/types.js +3 -0
  335. package/dist/session/types.js.map +1 -0
  336. package/dist/workspace/index.d.ts +7 -0
  337. package/dist/workspace/index.js +17 -0
  338. package/dist/workspace/index.js.map +1 -0
  339. package/dist/workspace/registry.d.ts +25 -0
  340. package/dist/workspace/registry.js +293 -0
  341. package/dist/workspace/registry.js.map +1 -0
  342. package/package.json +73 -5
  343. package/scripts/build-node-pty.mjs +55 -0
  344. package/scripts/rewrite-dts.mjs +36 -0
  345. package/scripts/verify-sandbox.ts +98 -0
  346. package/src/agent/pending-asks.ts +88 -0
  347. package/src/agent/task-registry.ts +108 -0
  348. package/src/agent/tool-context.ts +47 -0
  349. package/src/agent/usage.ts +170 -0
  350. package/src/api/host/agent.ts +162 -0
  351. package/src/api/host/events.ts +7 -0
  352. package/src/api/host/exec.ts +47 -0
  353. package/src/api/host/fs.ts +33 -0
  354. package/src/api/host/hooks.ts +635 -0
  355. package/src/api/host/host.ts +99 -0
  356. package/src/api/host/http.ts +79 -0
  357. package/src/api/host/index.ts +20 -0
  358. package/src/api/host/models.ts +81 -0
  359. package/src/api/host/network.ts +32 -0
  360. package/src/api/host/plugin.ts +158 -0
  361. package/src/api/host/plugins.ts +55 -0
  362. package/src/api/host/pty.ts +73 -0
  363. package/src/api/host/session.ts +61 -0
  364. package/src/api/host/version.ts +2 -0
  365. package/src/api/host/workspace.ts +51 -0
  366. package/src/api/plugin/http-client.ts +492 -0
  367. package/src/api/plugin/http-protocol.ts +231 -0
  368. package/src/api/result/result.ts +52 -0
  369. package/src/commands/agent-turn.ts +817 -0
  370. package/src/commands/ask-prompt.ts +52 -0
  371. package/src/commands/config.ts +306 -0
  372. package/src/commands/conversation.ts +777 -0
  373. package/src/commands/doctor.ts +92 -0
  374. package/src/commands/exec-approval.ts +91 -0
  375. package/src/commands/help.ts +41 -0
  376. package/src/commands/key.ts +281 -0
  377. package/src/commands/plugin.ts +218 -0
  378. package/src/commands/session.ts +179 -0
  379. package/src/commands/shared.ts +103 -0
  380. package/src/commands/start.ts +88 -0
  381. package/src/commands/stop.ts +24 -0
  382. package/src/commands/version.ts +40 -0
  383. package/src/config/codes.ts +17 -0
  384. package/src/config/credentials.ts +302 -0
  385. package/src/config/defaults.ts +32 -0
  386. package/src/config/index.ts +10 -0
  387. package/src/config/loader.ts +80 -0
  388. package/src/config/providers.ts +133 -0
  389. package/src/config/types.ts +88 -0
  390. package/src/config/validate.ts +458 -0
  391. package/src/display/markdown.ts +137 -0
  392. package/src/display/tool-label.ts +87 -0
  393. package/src/event/event.ts +171 -0
  394. package/src/index.ts +344 -0
  395. package/src/input/codes.ts +21 -0
  396. package/src/input/commands.ts +76 -0
  397. package/src/input/help.ts +60 -0
  398. package/src/input/index.ts +10 -0
  399. package/src/input/parse-options.ts +66 -0
  400. package/src/input/parse.ts +138 -0
  401. package/src/input/types.ts +19 -0
  402. package/src/logger/elog4js.ts +366 -0
  403. package/src/logger/logger.ts +251 -0
  404. package/src/network/index.ts +6 -0
  405. package/src/network/openai.ts +571 -0
  406. package/src/network/proxy.ts +80 -0
  407. package/src/network/web.ts +206 -0
  408. package/src/platform/clipboard.ts +107 -0
  409. package/src/platform/exec.ts +128 -0
  410. package/src/platform/file.ts +235 -0
  411. package/src/platform/index.ts +12 -0
  412. package/src/platform/notification.ts +108 -0
  413. package/src/platform/open-external.ts +132 -0
  414. package/src/platform/os.ts +101 -0
  415. package/src/platform/path.ts +214 -0
  416. package/src/plugin/access-guard.ts +46 -0
  417. package/src/plugin/agent-service.ts +969 -0
  418. package/src/plugin/agent-tools.ts +43 -0
  419. package/src/plugin/capabilities.ts +240 -0
  420. package/src/plugin/console-guard.ts +66 -0
  421. package/src/plugin/data-access.ts +70 -0
  422. package/src/plugin/fs.ts +109 -0
  423. package/src/plugin/host-alias.ts +61 -0
  424. package/src/plugin/index.ts +11 -0
  425. package/src/plugin/install.ts +149 -0
  426. package/src/plugin/loader.ts +952 -0
  427. package/src/plugin/manage.ts +329 -0
  428. package/src/plugin/models-service.ts +383 -0
  429. package/src/plugin/network-service.ts +102 -0
  430. package/src/plugin/paths.ts +19 -0
  431. package/src/plugin/registry.ts +103 -0
  432. package/src/plugin/runtime.ts +57 -0
  433. package/src/plugin/scaffold.ts +152 -0
  434. package/src/plugin/state.ts +52 -0
  435. package/src/plugin/zip.ts +156 -0
  436. package/src/sandbox/audit.ts +38 -0
  437. package/src/sandbox/backends/bwrap.ts +251 -0
  438. package/src/sandbox/backends/direct.ts +43 -0
  439. package/src/sandbox/bridge-state.ts +29 -0
  440. package/src/sandbox/index.ts +5 -0
  441. package/src/sandbox/manager.ts +406 -0
  442. package/src/sandbox/memory-limit.ts +245 -0
  443. package/src/sandbox/plugin-client.ts +1881 -0
  444. package/src/sandbox/plugin-host.ts +1282 -0
  445. package/src/sandbox/protocol.ts +142 -0
  446. package/src/sandbox/pty-bridge.py +154 -0
  447. package/src/sandbox/rpc-timeouts.ts +43 -0
  448. package/src/sandbox/supervisor.ts +172 -0
  449. package/src/sandbox/types.ts +149 -0
  450. package/src/service/control.ts +155 -0
  451. package/src/session/compress.ts +47 -0
  452. package/src/session/index.ts +8 -0
  453. package/src/session/registry.ts +17 -0
  454. package/src/session/replay.ts +132 -0
  455. package/src/session/session.ts +134 -0
  456. package/src/session/store.ts +468 -0
  457. package/src/session/types.ts +61 -0
  458. package/src/workspace/index.ts +21 -0
  459. package/src/workspace/registry.ts +356 -0
@@ -0,0 +1,571 @@
1
+ /*
2
+ * OpenAI SDK 接入。
3
+ * Responses API 为默认文本接口;chat 兼容只提供 Chat Completions 的网关。
4
+ * API key 读取顺序:显式参数 > OPENAI_API_KEY > <configDir>/credentials.json,
5
+ * 明文只进请求,不进配置、日志与界面。
6
+ */
7
+
8
+ import OpenAI, {
9
+ APIConnectionError,
10
+ APIConnectionTimeoutError,
11
+ APIError,
12
+ APIUserAbortError,
13
+ AuthenticationError,
14
+ BadRequestError,
15
+ InternalServerError,
16
+ NotFoundError,
17
+ PermissionDeniedError,
18
+ RateLimitError,
19
+ type ClientOptions,
20
+ } from "openai"
21
+ import type {
22
+ ChatCompletionMessageParam,
23
+ ChatCompletionTool,
24
+ } from "openai/resources/chat/completions"
25
+ import type { Logger } from "pino"
26
+
27
+ import { parseProviderUsage, type ProviderUsage } from "../agent/usage.ts"
28
+ import { Result } from "../api/result/result.ts"
29
+ import { normalizeApiKey, resolveApiKeySync } from "../config/credentials.ts"
30
+ import { DEFAULT_SETTINGS } from "../config/defaults.ts"
31
+ import type { OpenAISetting } from "../config/types.ts"
32
+ import { readEnv } from "../platform/os.ts"
33
+
34
+ /** OpenAI 调用错误码 */
35
+ export const OPENAI_CODES = {
36
+ missingApiKey: "openai.missing-api-key",
37
+ clientInitFailed: "openai.client-init-failed",
38
+ emptyOutput: "openai.empty-output",
39
+ timeout: "openai.timeout",
40
+ connectionFailed: "openai.connection-failed",
41
+ aborted: "openai.aborted",
42
+ unauthorized: "openai.unauthorized",
43
+ forbidden: "openai.forbidden",
44
+ notFound: "openai.not-found",
45
+ rateLimited: "openai.rate-limited",
46
+ badRequest: "openai.bad-request",
47
+ serverError: "openai.server-error",
48
+ apiError: "openai.api-error",
49
+ requestFailed: "openai.request-failed",
50
+ } as const
51
+
52
+ export interface OpenAIReply {
53
+ readonly text: string
54
+ readonly model: string
55
+ readonly id: string
56
+ readonly inputTokens: number | null
57
+ readonly outputTokens: number | null
58
+ /** 缓存命中 / 写入的 prompt tokens;供应商没报告时缺省 */
59
+ readonly cacheReadTokens?: number | null
60
+ readonly cacheWriteTokens?: number | null
61
+ /** 输出里的推理 token;供应商没报告时缺省 */
62
+ readonly reasoningTokens?: number | null
63
+ }
64
+
65
+ /** 把供应商 usage 里的缓存/推理项展开成可选字段,空值不写进回复对象 */
66
+ function providerUsageFields(
67
+ usage: ProviderUsage | null,
68
+ ): Pick<OpenAIReply, "cacheReadTokens" | "cacheWriteTokens" | "reasoningTokens"> {
69
+ if (usage === null) {
70
+ return {}
71
+ }
72
+ return {
73
+ ...(usage.cacheReadTokens === null ? {} : { cacheReadTokens: usage.cacheReadTokens }),
74
+ ...(usage.cacheWriteTokens === null ? {} : { cacheWriteTokens: usage.cacheWriteTokens }),
75
+ ...(usage.reasoningTokens === null ? {} : { reasoningTokens: usage.reasoningTokens }),
76
+ }
77
+ }
78
+
79
+ export interface RespondOptions {
80
+ readonly model?: string
81
+ readonly instructions?: string
82
+ readonly maxOutputTokens?: number
83
+ }
84
+
85
+ export interface ChatOptions {
86
+ readonly model?: string
87
+ readonly temperature?: number
88
+ readonly maxTokens?: number
89
+ /** 推理强度;off 表示不发送 reasoning 参数 */
90
+ readonly reasoning?: string
91
+ }
92
+
93
+ /** 规范化后的流式片段,直接落进会话事件 */
94
+ export interface OpenAISessionChunk {
95
+ readonly type: "chunk"
96
+ readonly time: number
97
+ readonly chunk: Record<string, unknown>
98
+ }
99
+
100
+ export interface ToolCall {
101
+ readonly id: string
102
+ readonly name: string
103
+ readonly arguments: string
104
+ }
105
+
106
+ export interface StreamReply {
107
+ /** 这次流被 signal 中断(用户停止);reply 里是已经收到的部分 */
108
+ readonly interrupted?: boolean
109
+ readonly reply: OpenAIReply
110
+ readonly chunks: readonly OpenAISessionChunk[]
111
+ readonly toolCalls: readonly ToolCall[]
112
+ /** 模型返回的思考内容(DeepSeek 等推理模型的 reasoning_content);没有时为空串 */
113
+ readonly reasoning: string
114
+ }
115
+
116
+ export interface ChatStreamOptions extends ChatOptions {
117
+ /**
118
+ * 中断信号:用户停止时立刻断开这次请求
119
+ *
120
+ * 已经收到的增量照常返回(`interrupted: true`),调用方按「用户停止」收尾,
121
+ * 不会因为打断丢掉半截回复。
122
+ */
123
+ readonly signal?: AbortSignal
124
+ readonly onText?: (text: string) => void
125
+ /** 思考内容增量回调;只在模型返回 reasoning_content 时触发 */
126
+ readonly onReasoning?: (text: string) => void
127
+ readonly tools?: readonly ChatCompletionTool[]
128
+ readonly toolChoice?: "auto" | "none" | "required"
129
+ }
130
+
131
+ export interface CreateOpenAIClientOptions {
132
+ readonly settings?: OpenAISetting
133
+ /** 覆盖 OPENAI_API_KEY 与本地凭据 */
134
+ readonly apiKey?: string
135
+ /** 本地凭据文件路径;不传用当前 configDir 下的 credentials.json */
136
+ readonly credentialsFile?: string
137
+ readonly logger?: Logger
138
+ /** 注入 fetch,测试用 */
139
+ readonly fetch?: ClientOptions["fetch"]
140
+ }
141
+
142
+ /**
143
+ * 推理强度映射:
144
+ * off 不发参数;low / high / max 原样发给 OpenAI 兼容端点。
145
+ * (标准 OpenAI 只定义到 high;若端点不接受 max 会返回 400,可换 high。)
146
+ */
147
+ function reasoningParams(level: string | undefined): Record<string, unknown> {
148
+ if (level === undefined || level === "" || level === "off") {
149
+ return {}
150
+ }
151
+ return { reasoning_effort: level }
152
+ }
153
+
154
+ /** 把 SDK 异常归一化为错误码 */
155
+ export function mapOpenAIError(error: unknown): string {
156
+ if (error instanceof APIUserAbortError) {
157
+ return OPENAI_CODES.aborted
158
+ }
159
+ // APIConnectionTimeoutError 是 APIConnectionError 的子类,先判断超时
160
+ if (error instanceof APIConnectionTimeoutError) {
161
+ return OPENAI_CODES.timeout
162
+ }
163
+ if (error instanceof APIConnectionError) {
164
+ return OPENAI_CODES.connectionFailed
165
+ }
166
+ if (error instanceof AuthenticationError) {
167
+ return OPENAI_CODES.unauthorized
168
+ }
169
+ if (error instanceof PermissionDeniedError) {
170
+ return OPENAI_CODES.forbidden
171
+ }
172
+ if (error instanceof NotFoundError) {
173
+ return OPENAI_CODES.notFound
174
+ }
175
+ if (error instanceof RateLimitError) {
176
+ return OPENAI_CODES.rateLimited
177
+ }
178
+ if (error instanceof BadRequestError) {
179
+ return OPENAI_CODES.badRequest
180
+ }
181
+ if (error instanceof InternalServerError) {
182
+ return OPENAI_CODES.serverError
183
+ }
184
+ if (error instanceof APIError) {
185
+ return `${OPENAI_CODES.apiError}:${error.status ?? "unknown"}`
186
+ }
187
+ return OPENAI_CODES.requestFailed
188
+ }
189
+
190
+ export class OpenAIClient {
191
+ readonly #client: OpenAI
192
+ readonly #settings: OpenAISetting
193
+ readonly #logger: Logger | undefined
194
+
195
+ private constructor(client: OpenAI, settings: OpenAISetting, logger?: Logger) {
196
+ this.#client = client
197
+ this.#settings = settings
198
+ this.#logger = logger
199
+ }
200
+
201
+ /** 创建客户端。缺少 API key 或 SDK 初始化失败时返回错误码 */
202
+ static create(options: CreateOpenAIClientOptions = {}): Result<OpenAIClient> {
203
+ const settings = options.settings ?? DEFAULT_SETTINGS.openai
204
+ const explicit = options.apiKey === undefined ? null : normalizeApiKey(options.apiKey)
205
+ if (options.apiKey !== undefined && explicit === null) {
206
+ return Result.error<OpenAIClient>(OPENAI_CODES.missingApiKey)
207
+ }
208
+ const apiKey =
209
+ explicit ??
210
+ (options.credentialsFile === undefined
211
+ ? resolveApiKeySync()
212
+ : resolveApiKeySync(options.credentialsFile)
213
+ )?.apiKey ??
214
+ null
215
+ if (apiKey === null) {
216
+ return Result.error<OpenAIClient>(OPENAI_CODES.missingApiKey)
217
+ }
218
+
219
+ try {
220
+ const client = new OpenAI({
221
+ apiKey,
222
+ timeout: settings.timeoutMs,
223
+ maxRetries: settings.maxRetries,
224
+ ...(settings.baseUrl === null ? {} : { baseURL: settings.baseUrl }),
225
+ ...(options.fetch === undefined ? {} : { fetch: options.fetch }),
226
+ })
227
+ return Result.successWithData(new OpenAIClient(client, settings, options.logger))
228
+ } catch {
229
+ return Result.error<OpenAIClient>(OPENAI_CODES.clientInitFailed)
230
+ }
231
+ }
232
+
233
+ /** 单轮文本生成,走 Responses API */
234
+ async respond(input: string, options: RespondOptions = {}): Promise<Result<OpenAIReply>> {
235
+ const model = options.model ?? this.#settings.model
236
+ try {
237
+ const response = await this.#client.responses.create({
238
+ model,
239
+ input,
240
+ ...(options.instructions === undefined
241
+ ? {}
242
+ : { instructions: options.instructions }),
243
+ ...(options.maxOutputTokens === undefined
244
+ ? {}
245
+ : { max_output_tokens: options.maxOutputTokens }),
246
+ })
247
+ const text = response.output_text
248
+ if (text === "") {
249
+ return Result.error<OpenAIReply>(OPENAI_CODES.emptyOutput)
250
+ }
251
+ this.#logger?.debug({ model: response.model, id: response.id }, "OpenAI Responses 调用")
252
+ const usage = parseProviderUsage(response.usage)
253
+ return Result.successWithData<OpenAIReply>({
254
+ text,
255
+ model: response.model,
256
+ id: response.id,
257
+ inputTokens: usage?.inputTokens ?? null,
258
+ outputTokens: usage?.outputTokens ?? null,
259
+ ...providerUsageFields(usage),
260
+ })
261
+ } catch (error) {
262
+ return this.failure(error, model)
263
+ }
264
+ }
265
+
266
+ /** 多轮对话,走 Chat Completions API */
267
+ async chat(
268
+ messages: readonly ChatCompletionMessageParam[],
269
+ options: ChatOptions = {},
270
+ ): Promise<Result<OpenAIReply>> {
271
+ const model = options.model ?? this.#settings.model
272
+ try {
273
+ const completion = await this.#client.chat.completions.create({
274
+ model,
275
+ messages: [...messages],
276
+ ...(options.temperature === undefined ? {} : { temperature: options.temperature }),
277
+ ...(options.maxTokens === undefined
278
+ ? {}
279
+ : { max_completion_tokens: options.maxTokens }),
280
+ ...reasoningParams(options.reasoning),
281
+ })
282
+ const text = completion.choices[0]?.message.content ?? ""
283
+ if (text === "") {
284
+ return Result.error<OpenAIReply>(OPENAI_CODES.emptyOutput)
285
+ }
286
+ this.#logger?.debug({ model: completion.model, id: completion.id }, "OpenAI Chat 调用")
287
+ const usage = parseProviderUsage(completion.usage)
288
+ return Result.successWithData<OpenAIReply>({
289
+ text,
290
+ model: completion.model,
291
+ id: completion.id,
292
+ inputTokens: usage?.inputTokens ?? null,
293
+ outputTokens: usage?.outputTokens ?? null,
294
+ ...providerUsageFields(usage),
295
+ })
296
+ } catch (error) {
297
+ return this.failure(error, model)
298
+ }
299
+ }
300
+
301
+ /** 流式 Chat Completions;始终请求流式,便于完整记录 chunk */
302
+ async chatStream(
303
+ messages: readonly ChatCompletionMessageParam[],
304
+ options: ChatStreamOptions = {},
305
+ ): Promise<Result<StreamReply>> {
306
+ const model = options.model ?? this.#settings.model
307
+ /* 累积状态放在 try 外:被 signal 中断时也要把已收到的部分交回去 */
308
+ const chunks: OpenAISessionChunk[] = []
309
+ let text = ""
310
+ let reasoning = ""
311
+ let id = ""
312
+ let responseModel = model
313
+ let streamUsage: ProviderUsage | null = null
314
+ let finishReason: string | null = null
315
+ let blockStarted = false
316
+ const toolCallMap = new Map<number, { id: string; name: string; arguments: string }>()
317
+ const collectedToolCalls = (): ToolCall[] =>
318
+ [...toolCallMap.entries()]
319
+ .sort(([left], [right]) => left - right)
320
+ .map(([index, entry]) => ({
321
+ id: entry.id === "" ? `call_${index}` : entry.id,
322
+ name: entry.name,
323
+ arguments: entry.arguments,
324
+ }))
325
+ try {
326
+ const stream = await this.#client.chat.completions.create(
327
+ {
328
+ model,
329
+ messages: [...messages],
330
+ stream: true,
331
+ stream_options: { include_usage: true },
332
+ ...(options.temperature === undefined
333
+ ? {}
334
+ : { temperature: options.temperature }),
335
+ ...(options.maxTokens === undefined
336
+ ? {}
337
+ : { max_completion_tokens: options.maxTokens }),
338
+ ...reasoningParams(options.reasoning),
339
+ ...(options.tools === undefined || options.tools.length === 0
340
+ ? {}
341
+ : {
342
+ tools: [...options.tools],
343
+ tool_choice: options.toolChoice ?? "auto",
344
+ }),
345
+ },
346
+ // signal 是请求选项(第二个参数),放进 body 只会被当成普通字段忽略
347
+ options.signal === undefined ? {} : { signal: options.signal },
348
+ )
349
+
350
+ const push = (chunk: Record<string, unknown>): void => {
351
+ chunks.push({ type: "chunk", time: Date.now(), chunk })
352
+ }
353
+
354
+ for await (const part of stream) {
355
+ if (part.id !== "") {
356
+ id = part.id
357
+ }
358
+ if (part.model !== "") {
359
+ responseModel = part.model
360
+ }
361
+ if (part.usage !== undefined && part.usage !== null) {
362
+ streamUsage = parseProviderUsage(part.usage)
363
+ push({
364
+ type: "usage",
365
+ usage: {
366
+ inputTokens: streamUsage?.inputTokens ?? null,
367
+ outputTokens: streamUsage?.outputTokens ?? null,
368
+ totalTokens: part.usage.total_tokens,
369
+ ...(streamUsage?.cacheReadTokens === null ||
370
+ streamUsage?.cacheReadTokens === undefined
371
+ ? {}
372
+ : { cacheReadTokens: streamUsage.cacheReadTokens }),
373
+ ...(streamUsage?.cacheWriteTokens === null ||
374
+ streamUsage?.cacheWriteTokens === undefined
375
+ ? {}
376
+ : { cacheWriteTokens: streamUsage.cacheWriteTokens }),
377
+ },
378
+ })
379
+ }
380
+ const choice = part.choices[0]
381
+ if (choice === undefined) {
382
+ continue
383
+ }
384
+ if (choice.finish_reason !== null && choice.finish_reason !== undefined) {
385
+ finishReason = choice.finish_reason
386
+ }
387
+ for (const toolCallDelta of choice.delta.tool_calls ?? []) {
388
+ const index = toolCallDelta.index ?? toolCallMap.size
389
+ const entry = toolCallMap.get(index) ?? {
390
+ id: "",
391
+ name: "",
392
+ arguments: "",
393
+ }
394
+ if (typeof toolCallDelta.id === "string") {
395
+ entry.id = toolCallDelta.id
396
+ }
397
+ if (typeof toolCallDelta.function?.name === "string") {
398
+ entry.name += toolCallDelta.function.name
399
+ }
400
+ if (typeof toolCallDelta.function?.arguments === "string") {
401
+ entry.arguments += toolCallDelta.function.arguments
402
+ }
403
+ toolCallMap.set(index, entry)
404
+ push({
405
+ type: "tool-call-delta",
406
+ index,
407
+ id: toolCallDelta.id ?? null,
408
+ name: toolCallDelta.function?.name ?? null,
409
+ argumentsDelta: toolCallDelta.function?.arguments ?? null,
410
+ })
411
+ }
412
+ // 推理模型(DeepSeek 等)把思考内容放在 delta.reasoning_content,
413
+ // 部分网关用 delta.reasoning;两者都按增量拼接,不进正文
414
+ const rawDelta = choice.delta as {
415
+ readonly content?: unknown
416
+ readonly reasoning_content?: unknown
417
+ readonly reasoning?: unknown
418
+ }
419
+ const reasoningDelta =
420
+ typeof rawDelta.reasoning_content === "string"
421
+ ? rawDelta.reasoning_content
422
+ : typeof rawDelta.reasoning === "string"
423
+ ? rawDelta.reasoning
424
+ : ""
425
+ if (reasoningDelta !== "") {
426
+ reasoning += reasoningDelta
427
+ push({ type: "reasoning-delta", index: 1, text: reasoningDelta })
428
+ options.onReasoning?.(reasoningDelta)
429
+ }
430
+ const delta = rawDelta.content
431
+ if (typeof delta === "string" && delta !== "") {
432
+ if (!blockStarted) {
433
+ push({ type: "block-start", index: 0, blockType: "text" })
434
+ blockStarted = true
435
+ }
436
+ text += delta
437
+ push({ type: "text-delta", index: 0, text: delta })
438
+ options.onText?.(delta)
439
+ }
440
+ }
441
+
442
+ if (!blockStarted) {
443
+ push({ type: "block-start", index: 0, blockType: "text" })
444
+ push({ type: "block-end", index: 0, block: { type: "text", text } })
445
+ } else {
446
+ push({ type: "block-end", index: 0, block: { type: "text", text } })
447
+ }
448
+ const toolCalls = collectedToolCalls()
449
+ for (const toolCall of toolCalls) {
450
+ push({ type: "tool-call", ...toolCall })
451
+ }
452
+ push({ type: "finish", reason: { kind: finishReason ?? "stop" } })
453
+
454
+ if (text === "" && toolCalls.length === 0) {
455
+ return Result.error<StreamReply>(OPENAI_CODES.emptyOutput)
456
+ }
457
+ this.#logger?.debug({ model: responseModel, id }, "OpenAI Chat 流式调用")
458
+ return Result.successWithData<StreamReply>({
459
+ reply: {
460
+ text,
461
+ model: responseModel,
462
+ id,
463
+ inputTokens: streamUsage?.inputTokens ?? null,
464
+ outputTokens: streamUsage?.outputTokens ?? null,
465
+ ...providerUsageFields(streamUsage),
466
+ },
467
+ chunks,
468
+ toolCalls,
469
+ reasoning,
470
+ })
471
+ } catch (error) {
472
+ if (options.signal?.aborted === true) {
473
+ /*
474
+ * 用户主动停止:不当作失败。把已经收到的正文/思考/工具调用照常交回去,
475
+ * 由调用方按「已停止」收尾——半截回复比清空更有用。
476
+ */
477
+ this.#logger?.debug({ model }, "OpenAI 流式调用被用户中断")
478
+ return Result.successWithData<StreamReply>({
479
+ interrupted: true,
480
+ reply: {
481
+ text,
482
+ model: responseModel,
483
+ id,
484
+ inputTokens: streamUsage?.inputTokens ?? null,
485
+ outputTokens: streamUsage?.outputTokens ?? null,
486
+ ...providerUsageFields(streamUsage),
487
+ },
488
+ chunks,
489
+ toolCalls: collectedToolCalls(),
490
+ reasoning,
491
+ })
492
+ }
493
+ const code = mapOpenAIError(error)
494
+ const status = error instanceof APIError ? (error.status ?? null) : null
495
+ const reason = error instanceof Error ? error.message : String(error)
496
+ this.#logger?.warn({ code, model, status, reason }, "OpenAI 流式调用失败")
497
+ return Result.error<StreamReply>(code)
498
+ }
499
+ }
500
+
501
+ /** 拉取供应商的模型 id 列表(用于自定义供应商的“拉取模型”) */
502
+ async listModels(): Promise<Result<readonly string[]>> {
503
+ try {
504
+ const page = await this.#client.models.list()
505
+ const models = (page.data ?? [])
506
+ .map((item) => (typeof item?.id === "string" ? item.id.trim() : ""))
507
+ .filter((id) => id !== "")
508
+ if (models.length === 0) {
509
+ return Result.error<readonly string[]>(OPENAI_CODES.emptyOutput)
510
+ }
511
+ return Result.successWithData<readonly string[]>(models.sort())
512
+ } catch (error) {
513
+ const code = mapOpenAIError(error)
514
+ const reason = error instanceof Error ? error.message : String(error)
515
+ this.#logger?.warn({ code, reason }, "拉取模型列表失败")
516
+ return Result.error<readonly string[]>(code)
517
+ }
518
+ }
519
+
520
+ /** 轻量连通性测试:拉模型列表,不产生对话费用 */
521
+ async testConnection(): Promise<Result<{ readonly models: number }>> {
522
+ try {
523
+ const page = await this.#client.models.list()
524
+ const models = Array.isArray(page.data) ? page.data.length : 0
525
+ if (models === 0) {
526
+ return Result.error<{ readonly models: number }>(OPENAI_CODES.emptyOutput)
527
+ }
528
+ return Result.successWithData<{ readonly models: number }>({ models })
529
+ } catch (error) {
530
+ const code = mapOpenAIError(error)
531
+ const reason = error instanceof Error ? error.message : String(error)
532
+ this.#logger?.warn({ code, reason }, "OpenAI 连通性测试失败")
533
+ return Result.error<{ readonly models: number }>(code)
534
+ }
535
+ }
536
+
537
+ private failure(error: unknown, model: string): Result<OpenAIReply> {
538
+ const code = mapOpenAIError(error)
539
+ const status = error instanceof APIError ? (error.status ?? null) : null
540
+ const reason = error instanceof Error ? error.message : String(error)
541
+ this.#logger?.warn({ code, model, status, reason }, "OpenAI 调用失败")
542
+ return Result.error<OpenAIReply>(code)
543
+ }
544
+ }
545
+
546
+ let current: OpenAIClient | null = null
547
+
548
+ /** 初始化全局客户端,已初始化时直接返回 */
549
+ export function initOpenAIClient(options: CreateOpenAIClientOptions = {}): Result<OpenAIClient> {
550
+ if (current !== null) {
551
+ return Result.successWithData(current)
552
+ }
553
+ const created = OpenAIClient.create(options)
554
+ if (created.isSuccess()) {
555
+ current = created.getData()
556
+ }
557
+ return created
558
+ }
559
+
560
+ /** 取全局客户端,未初始化时抛错 */
561
+ export function getOpenAIClient(): OpenAIClient {
562
+ if (current === null) {
563
+ throw new Error("OpenAI 客户端尚未初始化:先调用 initOpenAIClient()")
564
+ }
565
+ return current
566
+ }
567
+
568
+ /** 清空全局客户端,供测试与关停使用 */
569
+ export function resetOpenAIClient(): void {
570
+ current = null
571
+ }
@@ -0,0 +1,80 @@
1
+ /*
2
+ * HTTP/HTTPS 代理。
3
+ * 配置来自 app-setting.json 的 network.proxy;通过 undici 的 ProxyAgent 生成自定义 fetch。
4
+ * 只支持 HTTP/HTTPS 代理,SOCKS5 后续再加。
5
+ */
6
+
7
+ import { ProxyAgent, fetch as undiciFetch } from "undici"
8
+
9
+ import type { ProxySetting } from "../config/types.ts"
10
+
11
+ /** 从 fetch 入参中取绝对 URL;取不到时返回 null */
12
+ function toUrl(input: string | URL | Request): URL | null {
13
+ if (typeof input === "string") {
14
+ try {
15
+ return new URL(input)
16
+ } catch {
17
+ return null
18
+ }
19
+ }
20
+ if (input instanceof URL) {
21
+ return input
22
+ }
23
+ try {
24
+ return new URL(input.url)
25
+ } catch {
26
+ return null
27
+ }
28
+ }
29
+
30
+ function normalizePattern(pattern: string): string {
31
+ return pattern
32
+ .trim()
33
+ .toLowerCase()
34
+ .replace(/^\*?\./, "")
35
+ }
36
+
37
+ /** 判断 URL 是否命中 noProxy。支持 `*`、`example.com`、`.example.com`、子域后缀 */
38
+ export function shouldBypassProxy(url: URL, noProxy: readonly string[]): boolean {
39
+ const host = url.hostname.toLowerCase()
40
+ for (const raw of noProxy) {
41
+ const pattern = normalizePattern(raw)
42
+ if (pattern === "") {
43
+ continue
44
+ }
45
+ if (pattern === "*") {
46
+ return true
47
+ }
48
+ if (host === pattern || host.endsWith(`.${pattern}`)) {
49
+ return true
50
+ }
51
+ }
52
+ return false
53
+ }
54
+
55
+ /**
56
+ * 生成走代理的 fetch;proxy 为 null 时返回 undefined,调用方沿用全局 fetch。
57
+ * OpenAI SDK 接受自定义 fetch,这里是唯一注入点。
58
+ */
59
+ export function createProxyFetch(proxy: ProxySetting | null): typeof undiciFetch | undefined {
60
+ if (proxy === null) {
61
+ return undefined
62
+ }
63
+
64
+ const agent = new ProxyAgent({ uri: proxy.url })
65
+ const proxyFetch = (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
66
+ const url = toUrl(input)
67
+ // 命中 noProxy 时用全局 fetch,避免代理自身不可达的主机被绕路
68
+ if (url !== null && shouldBypassProxy(url, proxy.noProxy)) {
69
+ return fetch(input as string, init as RequestInit) as Promise<Response>
70
+ }
71
+ return undiciFetch(
72
+ input as never,
73
+ {
74
+ ...(init as Record<string, unknown>),
75
+ dispatcher: agent,
76
+ } as never,
77
+ ) as Promise<Response>
78
+ }
79
+ return proxyFetch as unknown as typeof undiciFetch
80
+ }