@qjp88995/agent-runtime 0.0.0-stage → 0.1.1

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 (477) hide show
  1. package/dist/agent-extensions.d.ts +30 -0
  2. package/dist/agent-extensions.d.ts.map +1 -0
  3. package/dist/agent-extensions.js +4 -0
  4. package/dist/agent-extensions.js.map +1 -0
  5. package/dist/builtins/ask-outcome.d.ts +49 -0
  6. package/dist/builtins/ask-outcome.d.ts.map +1 -0
  7. package/dist/builtins/ask-outcome.js +128 -0
  8. package/dist/builtins/ask-outcome.js.map +1 -0
  9. package/dist/builtins/ask-schema.d.ts +110 -0
  10. package/dist/builtins/ask-schema.d.ts.map +1 -0
  11. package/dist/builtins/ask-schema.js +124 -0
  12. package/dist/builtins/ask-schema.js.map +1 -0
  13. package/dist/builtins/ask.service.d.ts +210 -0
  14. package/dist/builtins/ask.service.d.ts.map +1 -0
  15. package/dist/builtins/ask.service.js +373 -0
  16. package/dist/builtins/ask.service.js.map +1 -0
  17. package/dist/builtins/ask.tool.d.ts +49 -0
  18. package/dist/builtins/ask.tool.d.ts.map +1 -0
  19. package/dist/builtins/ask.tool.js +109 -0
  20. package/dist/builtins/ask.tool.js.map +1 -0
  21. package/dist/builtins/index.d.ts +8 -0
  22. package/dist/builtins/index.d.ts.map +1 -0
  23. package/dist/builtins/index.js +8 -0
  24. package/dist/builtins/index.js.map +1 -0
  25. package/dist/builtins/todo-schema.d.ts +90 -0
  26. package/dist/builtins/todo-schema.d.ts.map +1 -0
  27. package/dist/builtins/todo-schema.js +97 -0
  28. package/dist/builtins/todo-schema.js.map +1 -0
  29. package/dist/builtins/todo-settle.hook.d.ts +110 -0
  30. package/dist/builtins/todo-settle.hook.d.ts.map +1 -0
  31. package/dist/builtins/todo-settle.hook.js +174 -0
  32. package/dist/builtins/todo-settle.hook.js.map +1 -0
  33. package/dist/builtins/todo.tool.d.ts +59 -0
  34. package/dist/builtins/todo.tool.d.ts.map +1 -0
  35. package/dist/builtins/todo.tool.js +107 -0
  36. package/dist/builtins/todo.tool.js.map +1 -0
  37. package/dist/context/context-budget-tracker.d.ts +185 -0
  38. package/dist/context/context-budget-tracker.d.ts.map +1 -0
  39. package/dist/context/context-budget-tracker.js +400 -0
  40. package/dist/context/context-budget-tracker.js.map +1 -0
  41. package/dist/context/context-budget.d.ts +48 -0
  42. package/dist/context/context-budget.d.ts.map +1 -0
  43. package/dist/context/context-budget.js +89 -0
  44. package/dist/context/context-budget.js.map +1 -0
  45. package/dist/context/index.d.ts +3 -0
  46. package/dist/context/index.d.ts.map +1 -0
  47. package/dist/context/index.js +3 -0
  48. package/dist/context/index.js.map +1 -0
  49. package/dist/extensions/repeat-call.hook.d.ts +32 -0
  50. package/dist/extensions/repeat-call.hook.d.ts.map +1 -0
  51. package/dist/extensions/repeat-call.hook.js +54 -0
  52. package/dist/extensions/repeat-call.hook.js.map +1 -0
  53. package/dist/hooks/index.d.ts +3 -0
  54. package/dist/hooks/index.d.ts.map +1 -0
  55. package/dist/hooks/index.js +3 -0
  56. package/dist/hooks/index.js.map +1 -0
  57. package/dist/hooks/turn-hook.registry.d.ts +51 -0
  58. package/dist/hooks/turn-hook.registry.d.ts.map +1 -0
  59. package/dist/hooks/turn-hook.registry.js +233 -0
  60. package/dist/hooks/turn-hook.registry.js.map +1 -0
  61. package/dist/hooks/turn-hook.types.d.ts +179 -0
  62. package/dist/hooks/turn-hook.types.d.ts.map +1 -0
  63. package/dist/hooks/turn-hook.types.js +48 -0
  64. package/dist/hooks/turn-hook.types.js.map +1 -0
  65. package/dist/images/byte-cache.d.ts +24 -0
  66. package/dist/images/byte-cache.d.ts.map +1 -0
  67. package/dist/images/byte-cache.js +51 -0
  68. package/dist/images/byte-cache.js.map +1 -0
  69. package/dist/images/delivery.d.ts +115 -0
  70. package/dist/images/delivery.d.ts.map +1 -0
  71. package/dist/images/delivery.js +321 -0
  72. package/dist/images/delivery.js.map +1 -0
  73. package/dist/images/index.d.ts +5 -0
  74. package/dist/images/index.d.ts.map +1 -0
  75. package/dist/images/index.js +5 -0
  76. package/dist/images/index.js.map +1 -0
  77. package/dist/images/prompt.d.ts +151 -0
  78. package/dist/images/prompt.d.ts.map +1 -0
  79. package/dist/images/prompt.js +172 -0
  80. package/dist/images/prompt.js.map +1 -0
  81. package/dist/images/vendor-files.d.ts +93 -0
  82. package/dist/images/vendor-files.d.ts.map +1 -0
  83. package/dist/images/vendor-files.js +156 -0
  84. package/dist/images/vendor-files.js.map +1 -0
  85. package/dist/in-process/conversation-store.d.ts +64 -0
  86. package/dist/in-process/conversation-store.d.ts.map +1 -0
  87. package/dist/in-process/conversation-store.js +222 -0
  88. package/dist/in-process/conversation-store.js.map +1 -0
  89. package/dist/in-process/coordination.d.ts +54 -0
  90. package/dist/in-process/coordination.d.ts.map +1 -0
  91. package/dist/in-process/coordination.js +137 -0
  92. package/dist/in-process/coordination.js.map +1 -0
  93. package/dist/in-process/image-source.d.ts +15 -0
  94. package/dist/in-process/image-source.d.ts.map +1 -0
  95. package/dist/in-process/image-source.js +27 -0
  96. package/dist/in-process/image-source.js.map +1 -0
  97. package/dist/in-process/index.d.ts +36 -0
  98. package/dist/in-process/index.d.ts.map +1 -0
  99. package/dist/in-process/index.js +37 -0
  100. package/dist/in-process/index.js.map +1 -0
  101. package/dist/in-process/question-store.d.ts +28 -0
  102. package/dist/in-process/question-store.d.ts.map +1 -0
  103. package/dist/in-process/question-store.js +122 -0
  104. package/dist/in-process/question-store.js.map +1 -0
  105. package/dist/in-process/run-store.d.ts +36 -0
  106. package/dist/in-process/run-store.d.ts.map +1 -0
  107. package/dist/in-process/run-store.js +81 -0
  108. package/dist/in-process/run-store.js.map +1 -0
  109. package/dist/in-process/session-store.d.ts +33 -0
  110. package/dist/in-process/session-store.d.ts.map +1 -0
  111. package/dist/in-process/session-store.js +93 -0
  112. package/dist/in-process/session-store.js.map +1 -0
  113. package/dist/in-process/stream-bus.d.ts +52 -0
  114. package/dist/in-process/stream-bus.d.ts.map +1 -0
  115. package/dist/in-process/stream-bus.js +200 -0
  116. package/dist/in-process/stream-bus.js.map +1 -0
  117. package/dist/in-process/vendor-file-cache.d.ts +11 -0
  118. package/dist/in-process/vendor-file-cache.d.ts.map +1 -0
  119. package/dist/in-process/vendor-file-cache.js +47 -0
  120. package/dist/in-process/vendor-file-cache.js.map +1 -0
  121. package/dist/index.d.ts +21 -0
  122. package/dist/index.d.ts.map +1 -0
  123. package/dist/index.js +21 -0
  124. package/dist/index.js.map +1 -0
  125. package/dist/loop/active-tick.registry.d.ts +31 -0
  126. package/dist/loop/active-tick.registry.d.ts.map +1 -0
  127. package/dist/loop/active-tick.registry.js +45 -0
  128. package/dist/loop/active-tick.registry.js.map +1 -0
  129. package/dist/loop/index.d.ts +6 -0
  130. package/dist/loop/index.d.ts.map +1 -0
  131. package/dist/loop/index.js +6 -0
  132. package/dist/loop/index.js.map +1 -0
  133. package/dist/loop/loop-driver.d.ts +127 -0
  134. package/dist/loop/loop-driver.d.ts.map +1 -0
  135. package/dist/loop/loop-driver.js +199 -0
  136. package/dist/loop/loop-driver.js.map +1 -0
  137. package/dist/loop/message-hub.d.ts +236 -0
  138. package/dist/loop/message-hub.d.ts.map +1 -0
  139. package/dist/loop/message-hub.js +439 -0
  140. package/dist/loop/message-hub.js.map +1 -0
  141. package/dist/loop/orphan-run.sweeper.d.ts +79 -0
  142. package/dist/loop/orphan-run.sweeper.d.ts.map +1 -0
  143. package/dist/loop/orphan-run.sweeper.js +145 -0
  144. package/dist/loop/orphan-run.sweeper.js.map +1 -0
  145. package/dist/loop/root-host.d.ts +51 -0
  146. package/dist/loop/root-host.d.ts.map +1 -0
  147. package/dist/loop/root-host.js +65 -0
  148. package/dist/loop/root-host.js.map +1 -0
  149. package/dist/ports/context-store.d.ts +69 -0
  150. package/dist/ports/context-store.d.ts.map +1 -0
  151. package/dist/ports/context-store.js +2 -0
  152. package/dist/ports/context-store.js.map +1 -0
  153. package/dist/ports/conversation-store.d.ts +144 -0
  154. package/dist/ports/conversation-store.d.ts.map +1 -0
  155. package/dist/ports/conversation-store.js +2 -0
  156. package/dist/ports/conversation-store.js.map +1 -0
  157. package/dist/ports/coordination.d.ts +76 -0
  158. package/dist/ports/coordination.d.ts.map +1 -0
  159. package/dist/ports/coordination.js +12 -0
  160. package/dist/ports/coordination.js.map +1 -0
  161. package/dist/ports/image-source.d.ts +26 -0
  162. package/dist/ports/image-source.d.ts.map +1 -0
  163. package/dist/ports/image-source.js +2 -0
  164. package/dist/ports/image-source.js.map +1 -0
  165. package/dist/ports/index.d.ts +11 -0
  166. package/dist/ports/index.d.ts.map +1 -0
  167. package/dist/ports/index.js +4 -0
  168. package/dist/ports/index.js.map +1 -0
  169. package/dist/ports/logger.d.ts +14 -0
  170. package/dist/ports/logger.d.ts.map +1 -0
  171. package/dist/ports/logger.js +23 -0
  172. package/dist/ports/logger.js.map +1 -0
  173. package/dist/ports/question-store.d.ts +91 -0
  174. package/dist/ports/question-store.d.ts.map +1 -0
  175. package/dist/ports/question-store.js +8 -0
  176. package/dist/ports/question-store.js.map +1 -0
  177. package/dist/ports/run-store.d.ts +66 -0
  178. package/dist/ports/run-store.d.ts.map +1 -0
  179. package/dist/ports/run-store.js +2 -0
  180. package/dist/ports/run-store.js.map +1 -0
  181. package/dist/ports/session-store.d.ts +72 -0
  182. package/dist/ports/session-store.d.ts.map +1 -0
  183. package/dist/ports/session-store.js +9 -0
  184. package/dist/ports/session-store.js.map +1 -0
  185. package/dist/ports/stream-bus.d.ts +52 -0
  186. package/dist/ports/stream-bus.d.ts.map +1 -0
  187. package/dist/ports/stream-bus.js +2 -0
  188. package/dist/ports/stream-bus.js.map +1 -0
  189. package/dist/ports/vendor-file-cache.d.ts +46 -0
  190. package/dist/ports/vendor-file-cache.d.ts.map +1 -0
  191. package/dist/ports/vendor-file-cache.js +2 -0
  192. package/dist/ports/vendor-file-cache.js.map +1 -0
  193. package/dist/schema/agent-event.d.ts +35 -0
  194. package/dist/schema/agent-event.d.ts.map +1 -0
  195. package/dist/schema/agent-event.js +46 -0
  196. package/dist/schema/agent-event.js.map +1 -0
  197. package/dist/schema/agent-run.d.ts +23 -0
  198. package/dist/schema/agent-run.d.ts.map +1 -0
  199. package/dist/schema/agent-run.js +37 -0
  200. package/dist/schema/agent-run.js.map +1 -0
  201. package/dist/schema/index.d.ts +4 -0
  202. package/dist/schema/index.d.ts.map +1 -0
  203. package/dist/schema/index.js +4 -0
  204. package/dist/schema/index.js.map +1 -0
  205. package/dist/schema/message-part.d.ts +73 -0
  206. package/dist/schema/message-part.d.ts.map +1 -0
  207. package/dist/schema/message-part.js +90 -0
  208. package/dist/schema/message-part.js.map +1 -0
  209. package/dist/subagent/delegate.tool.d.ts +145 -0
  210. package/dist/subagent/delegate.tool.d.ts.map +1 -0
  211. package/dist/subagent/delegate.tool.js +329 -0
  212. package/dist/subagent/delegate.tool.js.map +1 -0
  213. package/dist/subagent/index.d.ts +10 -0
  214. package/dist/subagent/index.d.ts.map +1 -0
  215. package/dist/subagent/index.js +10 -0
  216. package/dist/subagent/index.js.map +1 -0
  217. package/dist/subagent/milestone-body.d.ts +82 -0
  218. package/dist/subagent/milestone-body.d.ts.map +1 -0
  219. package/dist/subagent/milestone-body.js +112 -0
  220. package/dist/subagent/milestone-body.js.map +1 -0
  221. package/dist/subagent/output-scan.d.ts +14 -0
  222. package/dist/subagent/output-scan.d.ts.map +1 -0
  223. package/dist/subagent/output-scan.js +41 -0
  224. package/dist/subagent/output-scan.js.map +1 -0
  225. package/dist/subagent/registry-spawn-policy.d.ts +32 -0
  226. package/dist/subagent/registry-spawn-policy.d.ts.map +1 -0
  227. package/dist/subagent/registry-spawn-policy.js +48 -0
  228. package/dist/subagent/registry-spawn-policy.js.map +1 -0
  229. package/dist/subagent/roster-block.d.ts +45 -0
  230. package/dist/subagent/roster-block.d.ts.map +1 -0
  231. package/dist/subagent/roster-block.js +69 -0
  232. package/dist/subagent/roster-block.js.map +1 -0
  233. package/dist/subagent/situation-block.d.ts +16 -0
  234. package/dist/subagent/situation-block.d.ts.map +1 -0
  235. package/dist/subagent/situation-block.js +71 -0
  236. package/dist/subagent/situation-block.js.map +1 -0
  237. package/dist/subagent/spawn-guards.d.ts +32 -0
  238. package/dist/subagent/spawn-guards.d.ts.map +1 -0
  239. package/dist/subagent/spawn-guards.js +39 -0
  240. package/dist/subagent/spawn-guards.js.map +1 -0
  241. package/dist/subagent/subagent-registry.d.ts +150 -0
  242. package/dist/subagent/subagent-registry.d.ts.map +1 -0
  243. package/dist/subagent/subagent-registry.js +167 -0
  244. package/dist/subagent/subagent-registry.js.map +1 -0
  245. package/dist/subagent/subagent-sessions.d.ts +213 -0
  246. package/dist/subagent/subagent-sessions.d.ts.map +1 -0
  247. package/dist/subagent/subagent-sessions.js +295 -0
  248. package/dist/subagent/subagent-sessions.js.map +1 -0
  249. package/dist/testing/contracts.d.ts +37 -0
  250. package/dist/testing/contracts.d.ts.map +1 -0
  251. package/dist/testing/contracts.js +607 -0
  252. package/dist/testing/contracts.js.map +1 -0
  253. package/dist/testing/index.d.ts +5 -0
  254. package/dist/testing/index.d.ts.map +1 -0
  255. package/dist/testing/index.js +5 -0
  256. package/dist/testing/index.js.map +1 -0
  257. package/dist/testing/stub.d.ts +28 -0
  258. package/dist/testing/stub.d.ts.map +1 -0
  259. package/dist/testing/stub.js +3 -0
  260. package/dist/testing/stub.js.map +1 -0
  261. package/dist/tools/agent-tool.base.d.ts +186 -0
  262. package/dist/tools/agent-tool.base.d.ts.map +1 -0
  263. package/dist/tools/agent-tool.base.js +243 -0
  264. package/dist/tools/agent-tool.base.js.map +1 -0
  265. package/dist/tools/agent-tool.interface.d.ts +95 -0
  266. package/dist/tools/agent-tool.interface.d.ts.map +1 -0
  267. package/dist/tools/agent-tool.interface.js +2 -0
  268. package/dist/tools/agent-tool.interface.js.map +1 -0
  269. package/dist/tools/builtin-names.d.ts +18 -0
  270. package/dist/tools/builtin-names.d.ts.map +1 -0
  271. package/dist/tools/builtin-names.js +20 -0
  272. package/dist/tools/builtin-names.js.map +1 -0
  273. package/dist/tools/index.d.ts +9 -0
  274. package/dist/tools/index.d.ts.map +1 -0
  275. package/dist/tools/index.js +6 -0
  276. package/dist/tools/index.js.map +1 -0
  277. package/dist/tools/tool-definition.d.ts +18 -0
  278. package/dist/tools/tool-definition.d.ts.map +1 -0
  279. package/dist/tools/tool-definition.js +77 -0
  280. package/dist/tools/tool-definition.js.map +1 -0
  281. package/dist/tools/tool-meta.d.ts +55 -0
  282. package/dist/tools/tool-meta.d.ts.map +1 -0
  283. package/dist/tools/tool-meta.js +2 -0
  284. package/dist/tools/tool-meta.js.map +1 -0
  285. package/dist/tools/tool-registry.d.ts +190 -0
  286. package/dist/tools/tool-registry.d.ts.map +1 -0
  287. package/dist/tools/tool-registry.js +379 -0
  288. package/dist/tools/tool-registry.js.map +1 -0
  289. package/dist/tools/tool-result.d.ts +41 -0
  290. package/dist/tools/tool-result.d.ts.map +1 -0
  291. package/dist/tools/tool-result.js +19 -0
  292. package/dist/tools/tool-result.js.map +1 -0
  293. package/dist/tools/tool-wire.d.ts +24 -0
  294. package/dist/tools/tool-wire.d.ts.map +1 -0
  295. package/dist/tools/tool-wire.js +2 -0
  296. package/dist/tools/tool-wire.js.map +1 -0
  297. package/dist/turn/agent-runtime.d.ts +31 -0
  298. package/dist/turn/agent-runtime.d.ts.map +1 -0
  299. package/dist/turn/agent-runtime.js +30 -0
  300. package/dist/turn/agent-runtime.js.map +1 -0
  301. package/dist/turn/chunk-projection.d.ts +32 -0
  302. package/dist/turn/chunk-projection.d.ts.map +1 -0
  303. package/dist/turn/chunk-projection.js +62 -0
  304. package/dist/turn/chunk-projection.js.map +1 -0
  305. package/dist/turn/compaction.d.ts +48 -0
  306. package/dist/turn/compaction.d.ts.map +1 -0
  307. package/dist/turn/compaction.js +89 -0
  308. package/dist/turn/compaction.js.map +1 -0
  309. package/dist/turn/context-overflow.d.ts +11 -0
  310. package/dist/turn/context-overflow.d.ts.map +1 -0
  311. package/dist/turn/context-overflow.js +53 -0
  312. package/dist/turn/context-overflow.js.map +1 -0
  313. package/dist/turn/context-pressure.d.ts +100 -0
  314. package/dist/turn/context-pressure.d.ts.map +1 -0
  315. package/dist/turn/context-pressure.js +21 -0
  316. package/dist/turn/context-pressure.js.map +1 -0
  317. package/dist/turn/events-to-steps.d.ts +8 -0
  318. package/dist/turn/events-to-steps.d.ts.map +1 -0
  319. package/dist/turn/events-to-steps.js +64 -0
  320. package/dist/turn/events-to-steps.js.map +1 -0
  321. package/dist/turn/index.d.ts +17 -0
  322. package/dist/turn/index.d.ts.map +1 -0
  323. package/dist/turn/index.js +16 -0
  324. package/dist/turn/index.js.map +1 -0
  325. package/dist/turn/json-preview.d.ts +22 -0
  326. package/dist/turn/json-preview.d.ts.map +1 -0
  327. package/dist/turn/json-preview.js +34 -0
  328. package/dist/turn/json-preview.js.map +1 -0
  329. package/dist/turn/json-string-field.d.ts +30 -0
  330. package/dist/turn/json-string-field.d.ts.map +1 -0
  331. package/dist/turn/json-string-field.js +125 -0
  332. package/dist/turn/json-string-field.js.map +1 -0
  333. package/dist/turn/loop-reminder.d.ts +79 -0
  334. package/dist/turn/loop-reminder.d.ts.map +1 -0
  335. package/dist/turn/loop-reminder.js +0 -0
  336. package/dist/turn/loop-reminder.js.map +1 -0
  337. package/dist/turn/nul-safe.d.ts +21 -0
  338. package/dist/turn/nul-safe.d.ts.map +1 -0
  339. package/dist/turn/nul-safe.js +23 -0
  340. package/dist/turn/nul-safe.js.map +1 -0
  341. package/dist/turn/parts-from-events.d.ts +71 -0
  342. package/dist/turn/parts-from-events.d.ts.map +1 -0
  343. package/dist/turn/parts-from-events.js +217 -0
  344. package/dist/turn/parts-from-events.js.map +1 -0
  345. package/dist/turn/parts-to-model-messages.d.ts +158 -0
  346. package/dist/turn/parts-to-model-messages.d.ts.map +1 -0
  347. package/dist/turn/parts-to-model-messages.js +334 -0
  348. package/dist/turn/parts-to-model-messages.js.map +1 -0
  349. package/dist/turn/pending-work.d.ts +10 -0
  350. package/dist/turn/pending-work.d.ts.map +1 -0
  351. package/dist/turn/pending-work.js +14 -0
  352. package/dist/turn/pending-work.js.map +1 -0
  353. package/dist/turn/provider-error.d.ts +45 -0
  354. package/dist/turn/provider-error.d.ts.map +1 -0
  355. package/dist/turn/provider-error.js +154 -0
  356. package/dist/turn/provider-error.js.map +1 -0
  357. package/dist/turn/tool-input-preview.d.ts +94 -0
  358. package/dist/turn/tool-input-preview.d.ts.map +1 -0
  359. package/dist/turn/tool-input-preview.js +143 -0
  360. package/dist/turn/tool-input-preview.js.map +1 -0
  361. package/dist/turn/tool-input-progress.d.ts +47 -0
  362. package/dist/turn/tool-input-progress.d.ts.map +1 -0
  363. package/dist/turn/tool-input-progress.js +50 -0
  364. package/dist/turn/tool-input-progress.js.map +1 -0
  365. package/dist/turn/turn-envelope.d.ts +184 -0
  366. package/dist/turn/turn-envelope.d.ts.map +1 -0
  367. package/dist/turn/turn-envelope.js +2 -0
  368. package/dist/turn/turn-envelope.js.map +1 -0
  369. package/dist/turn/turn-journal.d.ts +49 -0
  370. package/dist/turn/turn-journal.d.ts.map +1 -0
  371. package/dist/turn/turn-journal.js +51 -0
  372. package/dist/turn/turn-journal.js.map +1 -0
  373. package/dist/turn/turn-run.d.ts +487 -0
  374. package/dist/turn/turn-run.d.ts.map +1 -0
  375. package/dist/turn/turn-run.js +1167 -0
  376. package/dist/turn/turn-run.js.map +1 -0
  377. package/dist/turn/usage.d.ts +27 -0
  378. package/dist/turn/usage.d.ts.map +1 -0
  379. package/dist/turn/usage.js +51 -0
  380. package/dist/turn/usage.js.map +1 -0
  381. package/package.json +59 -3
  382. package/src/agent-extensions.ts +33 -0
  383. package/src/builtins/ask-outcome.ts +168 -0
  384. package/src/builtins/ask-schema.ts +186 -0
  385. package/src/builtins/ask.service.ts +482 -0
  386. package/src/builtins/ask.tool.ts +138 -0
  387. package/src/builtins/index.ts +25 -0
  388. package/src/builtins/todo-schema.ts +108 -0
  389. package/src/builtins/todo-settle.hook.ts +221 -0
  390. package/src/builtins/todo.tool.ts +134 -0
  391. package/src/context/context-budget-tracker.ts +519 -0
  392. package/src/context/context-budget.ts +124 -0
  393. package/src/context/index.ts +17 -0
  394. package/src/extensions/repeat-call.hook.ts +77 -0
  395. package/src/hooks/index.ts +19 -0
  396. package/src/hooks/turn-hook.registry.ts +307 -0
  397. package/src/hooks/turn-hook.types.ts +210 -0
  398. package/src/images/byte-cache.ts +61 -0
  399. package/src/images/delivery.ts +508 -0
  400. package/src/images/index.ts +35 -0
  401. package/src/images/prompt.ts +289 -0
  402. package/src/images/vendor-files.ts +235 -0
  403. package/src/in-process/conversation-store.ts +304 -0
  404. package/src/in-process/coordination.ts +177 -0
  405. package/src/in-process/image-source.ts +29 -0
  406. package/src/in-process/index.ts +52 -0
  407. package/src/in-process/question-store.ts +155 -0
  408. package/src/in-process/run-store.ts +115 -0
  409. package/src/in-process/session-store.ts +118 -0
  410. package/src/in-process/stream-bus.ts +244 -0
  411. package/src/in-process/vendor-file-cache.ts +67 -0
  412. package/src/index.ts +28 -0
  413. package/src/loop/active-tick.registry.ts +49 -0
  414. package/src/loop/index.ts +25 -0
  415. package/src/loop/loop-driver.ts +275 -0
  416. package/src/loop/message-hub.ts +532 -0
  417. package/src/loop/orphan-run.sweeper.ts +204 -0
  418. package/src/loop/root-host.ts +119 -0
  419. package/src/ports/context-store.ts +71 -0
  420. package/src/ports/conversation-store.ts +165 -0
  421. package/src/ports/coordination.ts +81 -0
  422. package/src/ports/image-source.ts +26 -0
  423. package/src/ports/index.ts +56 -0
  424. package/src/ports/logger.ts +34 -0
  425. package/src/ports/question-store.ts +117 -0
  426. package/src/ports/run-store.ts +69 -0
  427. package/src/ports/session-store.ts +80 -0
  428. package/src/ports/stream-bus.ts +59 -0
  429. package/src/ports/vendor-file-cache.ts +54 -0
  430. package/src/schema/agent-event.ts +69 -0
  431. package/src/schema/agent-run.ts +44 -0
  432. package/src/schema/index.ts +25 -0
  433. package/src/schema/message-part.ts +100 -0
  434. package/src/subagent/delegate.tool.ts +476 -0
  435. package/src/subagent/index.ts +63 -0
  436. package/src/subagent/milestone-body.ts +144 -0
  437. package/src/subagent/output-scan.ts +56 -0
  438. package/src/subagent/registry-spawn-policy.ts +85 -0
  439. package/src/subagent/roster-block.ts +103 -0
  440. package/src/subagent/situation-block.ts +83 -0
  441. package/src/subagent/spawn-guards.ts +54 -0
  442. package/src/subagent/subagent-registry.ts +283 -0
  443. package/src/subagent/subagent-sessions.ts +496 -0
  444. package/src/testing/contracts.ts +882 -0
  445. package/src/testing/index.ts +14 -0
  446. package/src/testing/stub.ts +28 -0
  447. package/src/tools/agent-tool.base.ts +361 -0
  448. package/src/tools/agent-tool.interface.ts +101 -0
  449. package/src/tools/builtin-names.ts +26 -0
  450. package/src/tools/index.ts +39 -0
  451. package/src/tools/tool-definition.ts +93 -0
  452. package/src/tools/tool-meta.ts +60 -0
  453. package/src/tools/tool-registry.ts +513 -0
  454. package/src/tools/tool-result.ts +45 -0
  455. package/src/tools/tool-wire.ts +24 -0
  456. package/src/turn/agent-runtime.ts +43 -0
  457. package/src/turn/chunk-projection.ts +81 -0
  458. package/src/turn/compaction.ts +103 -0
  459. package/src/turn/context-overflow.ts +54 -0
  460. package/src/turn/context-pressure.ts +114 -0
  461. package/src/turn/events-to-steps.ts +73 -0
  462. package/src/turn/index.ts +71 -0
  463. package/src/turn/json-preview.ts +36 -0
  464. package/src/turn/json-string-field.ts +134 -0
  465. package/src/turn/loop-reminder.ts +0 -0
  466. package/src/turn/nul-safe.ts +22 -0
  467. package/src/turn/parts-from-events.ts +248 -0
  468. package/src/turn/parts-to-model-messages.ts +428 -0
  469. package/src/turn/pending-work.ts +14 -0
  470. package/src/turn/provider-error.ts +160 -0
  471. package/src/turn/tool-input-preview.ts +223 -0
  472. package/src/turn/tool-input-progress.ts +76 -0
  473. package/src/turn/turn-envelope.ts +185 -0
  474. package/src/turn/turn-journal.ts +102 -0
  475. package/src/turn/turn-run.ts +1479 -0
  476. package/src/turn/usage.ts +56 -0
  477. package/README.md +0 -3
@@ -0,0 +1,289 @@
1
+ import { IMAGE_REFERENCE_KEY } from '../turn/parts-to-model-messages.js';
2
+
3
+ /**
4
+ * 图片递送的 prompt 层:把投影层发出的 `{[IMAGE_REFERENCE_KEY]: src}` 引用,在请求前变成
5
+ * 厂商真正吃得下的东西。只做重排 + 逐张交给 `ImageResolver`,三档的判据在 `delivery.ts`。
6
+ *
7
+ * ## 为什么在请求这一层、而不是投影层或 provider 包里
8
+ *
9
+ * - 投影层是纯函数、且「模型看得见的一切都必须能从库里重建」——库里只有 `src`。字节、URL、
10
+ * 厂商 file id 都是**这一次请求**的临时物,与历史无关。
11
+ * - chat-completions 的承载包把 tool-result 的 `content` 变体一律 `JSON.stringify`
12
+ * (`@ai-sdk/deepseek@3.0.31` / `@ai-sdk/openai-compatible@3.0.35` dist 已核),图放在工具
13
+ * 输出里根本到不了模型;`@ai-sdk/openai` 的 responses 认 `content` 里的 data/url 却不认
14
+ * `reference`(Files API 走不通)。所以两条协议**统一**:图从工具输出里剥出来,**挪进紧随
15
+ * 其后的一条合成 user 消息**——各承载包对 user 消息里的 file part 三种 data 形态都翻译得对。
16
+ * - 合成消息出现在 provider prompt 层,投影层 `ModelMessage[]` 条数不变,上下文预算的条数
17
+ * 守卫因此与它无关。
18
+ *
19
+ * ## 字节稳定
20
+ *
21
+ * 同一张图每一轮都要重发,前缀缓存要求它逐字节相同:`src` 内容不可变,这里只搬运不改动;
22
+ * 合成消息的文案也必须是 `src` / `path` / 工具名的确定函数(`DeliveryTexts` 由宿主换时同样要守)。
23
+ * **换档会破一次缓存**(首轮 base64、次轮 file id)——可接受,Files API 命中之后就稳定了。
24
+ */
25
+
26
+ /** 递送看到的 V4 prompt 片段——只声明本层读写的字段。 */
27
+ export type FileData =
28
+ | { type: 'data'; data: Uint8Array | string }
29
+ | { type: 'url'; url: URL }
30
+ | { type: 'reference'; reference: Record<string, string> }
31
+ | { type: 'text'; text: string };
32
+ export interface V4FilePart {
33
+ type: 'file';
34
+ data: FileData;
35
+ mediaType: string;
36
+ filename?: string;
37
+ providerOptions?: Record<string, Record<string, unknown>>;
38
+ }
39
+ export interface V4TextPart {
40
+ type: 'text';
41
+ text: string;
42
+ providerOptions?: Record<string, Record<string, unknown>>;
43
+ }
44
+ type V4ToolOutput =
45
+ | { type: 'text'; value: string }
46
+ | { type: 'content'; value: Array<V4TextPart | V4FilePart> }
47
+ | { type: string; value?: unknown };
48
+ interface V4ToolResultPart {
49
+ type: 'tool-result';
50
+ toolCallId: string;
51
+ toolName: string;
52
+ output: V4ToolOutput;
53
+ }
54
+ export type V4Message =
55
+ | { role: 'system'; content: string }
56
+ | { role: 'user'; content: Array<V4TextPart | V4FilePart> }
57
+ | { role: 'assistant'; content: unknown[] }
58
+ | { role: 'tool'; content: Array<V4ToolResultPart | { type: string }> };
59
+
60
+ /** 一张待递送的图:投影层引用 + 它挂在哪次工具调用上。 */
61
+ export interface PendingImage {
62
+ src: string;
63
+ mediaType: string;
64
+ path: string | null;
65
+ toolName: string;
66
+ }
67
+
68
+ /**
69
+ * 递送结果:一个能直接放进 user 消息的 file part,或一句解释。走了厂商 Files API 的那一档
70
+ * 带上 `vendorFileId`——请求被厂商以「file id 不存在」拒掉时要按它作废缓存。
71
+ */
72
+ export type DeliveredImage =
73
+ | { kind: 'file'; part: V4FilePart; vendorFileId?: string }
74
+ | { kind: 'unavailable'; reason: string };
75
+
76
+ export type ImageResolver = (image: PendingImage) => Promise<DeliveredImage>;
77
+
78
+ /**
79
+ * 合成消息的文案。缺省是中文;宿主可整套或逐条换,但每一条都必须是入参的确定函数
80
+ * (见头注「字节稳定」)。
81
+ */
82
+ export interface DeliveryTexts {
83
+ /** 一张图在合成消息里的标题行。`path` 缺席时报 src,好让模型知道是哪一张。 */
84
+ caption(image: PendingImage): string;
85
+ /** 图取不到时的那句话(接在标题后)。 */
86
+ unavailable: string;
87
+ /** 工具正文被剥掉图之后、正文又恰好为空时的占位(模型得知道图去了哪)。 */
88
+ stripped: string;
89
+ /**
90
+ * 合成 user 消息的尾注。这条消息挂着 `user` 角色,模型会把它读成「用户又发了一条消息、
91
+ * 还附了图」,于是有的模型(GLM)收到图之后再调一次看图工具去「处理这张新图」——白白多
92
+ * 一轮。角色改不了(图只在 user 消息里翻译得对),能做的是把话说明白。分两段:「不是新消息」
93
+ * 恒加(`trailer`);「别再调」只在至少一张图真送到时加(`trailerDelivered`)——图递不出去时
94
+ * 文案本身就在建议重新读取,再接一句「不必再调」就自相矛盾。
95
+ */
96
+ trailer: string;
97
+ trailerDelivered: string;
98
+ }
99
+
100
+ export const DEFAULT_DELIVERY_TEXTS: DeliveryTexts = {
101
+ caption: img => `【${img.toolName} 的附图】${img.path ?? `图片 ${img.src}`}`,
102
+ unavailable:
103
+ '这张图已取不到(可能被清理)。它若仍在工作区,可以让工具重新读取。',
104
+ stripped: '(本次结果是图片,见紧随其后的消息。)',
105
+ trailer: '(以上是你刚才那次工具调用返回的附图,不是用户发来的新消息。)',
106
+ trailerDelivered:
107
+ '(以上是你刚才那次工具调用返回的附图,不是用户发来的新消息;' +
108
+ '图已经在这里了,不必再调用工具查看同一张图。)',
109
+ };
110
+
111
+ function isOurImage(part: V4TextPart | V4FilePart): part is V4FilePart {
112
+ return (
113
+ part.type === 'file' &&
114
+ part.data.type === 'reference' &&
115
+ typeof part.data.reference[IMAGE_REFERENCE_KEY] === 'string'
116
+ );
117
+ }
118
+
119
+ function pendingOf(part: V4FilePart, toolName: string): PendingImage {
120
+ const data = part.data as { reference: Record<string, string> };
121
+ const opts = part.providerOptions?.[IMAGE_REFERENCE_KEY];
122
+ const path = opts && typeof opts.path === 'string' ? opts.path : null;
123
+ return {
124
+ src: data.reference[IMAGE_REFERENCE_KEY],
125
+ mediaType: part.mediaType,
126
+ path,
127
+ toolName,
128
+ };
129
+ }
130
+
131
+ function ourImagesOf(part: { type: string }): V4FilePart[] {
132
+ if (part.type !== 'tool-result') return [];
133
+ const out = (part as V4ToolResultPart).output;
134
+ if (out.type !== 'content' || !Array.isArray(out.value)) return [];
135
+ return (out.value as Array<V4TextPart | V4FilePart>).filter(isOurImage);
136
+ }
137
+
138
+ /** prompt 里全部待递送的图(按出现顺序,可能有重复的 src)。 */
139
+ export function collectPendingImages(
140
+ prompt: readonly V4Message[]
141
+ ): PendingImage[] {
142
+ const out: PendingImage[] = [];
143
+ for (const msg of prompt) {
144
+ if (msg.role !== 'tool') continue;
145
+ for (const part of msg.content) {
146
+ for (const f of ourImagesOf(part)) {
147
+ out.push(pendingOf(f, (part as V4ToolResultPart).toolName));
148
+ }
149
+ }
150
+ }
151
+ return out;
152
+ }
153
+
154
+ /**
155
+ * 把一份 prompt 里我们的图片引用全部解析掉。纯粹的重排 + 逐张 `resolve`(按出现顺序、
156
+ * 串行——第一张上传定下的账号要给后面的用),不碰别的消息。
157
+ */
158
+ export async function deliverImages(
159
+ prompt: readonly V4Message[],
160
+ resolve: ImageResolver,
161
+ texts: DeliveryTexts = DEFAULT_DELIVERY_TEXTS
162
+ ): Promise<V4Message[]> {
163
+ const out: V4Message[] = [];
164
+ for (const msg of prompt) {
165
+ if (msg.role !== 'tool') {
166
+ out.push(msg);
167
+ continue;
168
+ }
169
+ const pending: PendingImage[] = [];
170
+ const content = msg.content.map(part => {
171
+ const ours = ourImagesOf(part);
172
+ if (ours.length === 0) return part;
173
+ const tr = part as V4ToolResultPart;
174
+ for (const f of ours) pending.push(pendingOf(f, tr.toolName));
175
+ const rest = (tr.output.value as Array<V4TextPart | V4FilePart>).filter(
176
+ p => !isOurImage(p)
177
+ );
178
+ // 剥掉图之后若只剩文本,就退回最普通的 `text` 变体——各家对它的翻译最稳;若还有别的
179
+ // file part(不是我们的引用),保留 `content` 变体原样交出去。
180
+ const onlyText = rest.every(p => p.type === 'text');
181
+ const text = rest
182
+ .filter((p): p is V4TextPart => p.type === 'text')
183
+ .map(p => p.text)
184
+ .join('\n');
185
+ return {
186
+ ...tr,
187
+ output: onlyText
188
+ ? { type: 'text', value: text.trim() === '' ? texts.stripped : text }
189
+ : { type: 'content', value: rest },
190
+ };
191
+ });
192
+ out.push({ ...msg, content } as V4Message);
193
+ if (pending.length === 0) continue;
194
+
195
+ const parts: Array<V4TextPart | V4FilePart> = [];
196
+ let anyDelivered = false;
197
+ for (const img of pending) {
198
+ const delivered = await resolve(img);
199
+ if (delivered.kind === 'file') {
200
+ anyDelivered = true;
201
+ parts.push({ type: 'text', text: texts.caption(img) }, delivered.part);
202
+ } else {
203
+ parts.push({
204
+ type: 'text',
205
+ text: `${texts.caption(img)}——无法显示:${delivered.reason}`,
206
+ });
207
+ }
208
+ }
209
+ parts.push({
210
+ type: 'text',
211
+ text: anyDelivered ? texts.trailerDelivered : texts.trailer,
212
+ });
213
+ out.push({ role: 'user', content: parts });
214
+ }
215
+ return out;
216
+ }
217
+
218
+ /**
219
+ * 公网 URL 那一档的判据:承载包声明它会自己去取这类 URL。`supportedUrls` 的 key 是媒体
220
+ * 类型模式(`image/*`),值是 URL 正则数组——照 `ai` 核心 `isUrlSupported` 的比对方式。
221
+ * (`ai` 核心在 middleware 之前已经转换完 prompt,不会替我们下载,所以这一档必须自己判。)
222
+ */
223
+ export function urlSupported(
224
+ supportedUrls: Record<string, RegExp[]>,
225
+ mediaType: string,
226
+ url: string
227
+ ): boolean {
228
+ for (const [pattern, regexes] of Object.entries(supportedUrls)) {
229
+ if (!mediaTypeMatches(pattern, mediaType)) continue;
230
+ if (regexes.some(re => re.test(url))) return true;
231
+ }
232
+ return false;
233
+ }
234
+
235
+ function mediaTypeMatches(pattern: string, mediaType: string): boolean {
236
+ if (pattern === '*' || pattern === '*/*') return true;
237
+ if (pattern.endsWith('/*')) {
238
+ return mediaType
239
+ .toLowerCase()
240
+ .startsWith(pattern.slice(0, -1).toLowerCase());
241
+ }
242
+ return pattern.toLowerCase() === mediaType.toLowerCase();
243
+ }
244
+
245
+ /**
246
+ * 一个请求错误的全部文字:message、`responseBody`(AI SDK 的 `APICallError` 把中心 / 厂商的
247
+ * 原始响应体放在这里,message 往往只是厂商包解析过的一句)、`cause` 链。
248
+ */
249
+ export function errorText(err: unknown): string {
250
+ const seen = new Set<unknown>();
251
+ const parts: string[] = [];
252
+ let cur: unknown = err;
253
+ while (cur && !seen.has(cur)) {
254
+ seen.add(cur);
255
+ if (cur instanceof Error) parts.push(cur.message);
256
+ else parts.push(String(cur));
257
+ const body = (cur as { responseBody?: unknown }).responseBody;
258
+ if (typeof body === 'string') parts.push(body);
259
+ cur = (cur as { cause?: unknown }).cause;
260
+ }
261
+ return parts.join('\n');
262
+ }
263
+
264
+ /**
265
+ * 厂商拒绝了我们交出去的 file id 吗。判据是错误文本里**点名了**这次用到的 id——比匹配某家的
266
+ * 措辞稳(DeepSeek 的原文是「the following file_ids do not exist or are not created under
267
+ * your account: file-api-…」,探针 2026-08-27),也天然厂商无关。
268
+ */
269
+ export function mentionsVendorFile(
270
+ err: unknown,
271
+ vendorFileIds: readonly string[]
272
+ ): boolean {
273
+ return rejectedVendorFiles(err, vendorFileIds).length > 0;
274
+ }
275
+
276
+ /** 错误里点了名的那几个 id(只忘掉它们:别的文件还在,重传是白花配额)。 */
277
+ export function rejectedVendorFiles(
278
+ err: unknown,
279
+ vendorFileIds: readonly string[]
280
+ ): string[] {
281
+ if (vendorFileIds.length === 0) return [];
282
+ const text = errorText(err);
283
+ return [...new Set(vendorFileIds.filter(id => text.includes(id)))];
284
+ }
285
+
286
+ /** 中心说钉住的那把 key 不可用(409 `pinned_credential_unavailable`,不会自己切换)。 */
287
+ export function isPinnedCredentialUnavailable(err: unknown): boolean {
288
+ return errorText(err).includes('pinned_credential_unavailable');
289
+ }
@@ -0,0 +1,235 @@
1
+ import { type Logger, silentLogger } from '../ports/logger.js';
2
+ import type {
3
+ VendorFileCache,
4
+ VendorFileRecord,
5
+ } from '../ports/vendor-file-cache.js';
6
+
7
+ /** 直连模式(上传结果没有 `credentialRef`)下的账号名:只有一把 key,不 pin。 */
8
+ export const DIRECT_ACCOUNT = 'direct';
9
+
10
+ /**
11
+ * 到期前多久就当失效。DeepSeek 允许的最短 TTL 是 1h;6h 的边际保证一条长对话在我们判
12
+ * 「还有效」到请求真正发出之间不会跨过到期点。
13
+ */
14
+ export const VENDOR_FILE_EXPIRY_MARGIN_MS = 6 * 60 * 60 * 1000;
15
+ /**
16
+ * 上传 TTL 的下限:余量的两倍。TTL 短于余量的话文件一传上去就「不新鲜」,每一轮都重传
17
+ * (验收脚本配 1h 时踩到过);短于两倍则新鲜期还不到 TTL 的一半。宿主配得更短时抬到这里。
18
+ */
19
+ export const MIN_VENDOR_FILE_TTL_SECONDS =
20
+ (2 * VENDOR_FILE_EXPIRY_MARGIN_MS) / 1000;
21
+ /** `lastUsedAt` 的写入节流:它只服务于排查与挑 pin,不值得每个请求写一次。 */
22
+ const TOUCH_INTERVAL_MS = 60 * 60 * 1000;
23
+
24
+ /** 上传要的那一点 Files API(`ModelHandle.fileStore` 的结构子集)。 */
25
+ export interface VendorFileStoreLike {
26
+ /** `FilePart.data.reference` 的命名空间 key(跟承载包走)。 */
27
+ referenceKey: string;
28
+ upload(input: {
29
+ data: Uint8Array;
30
+ mediaType: string;
31
+ filename?: string;
32
+ ttlSeconds?: number;
33
+ }): Promise<{
34
+ fileId: string;
35
+ expiresAt: Date | null;
36
+ /** 中心模式:文件落在哪把 key 上;直连为 null。 */
37
+ credentialRef: string | null;
38
+ }>;
39
+ delete(fileId: string): Promise<void>;
40
+ }
41
+
42
+ export interface VendorFilesOptions {
43
+ cache: VendorFileCache;
44
+ /**
45
+ * 上传时的 `ttlSeconds`;省略或 ≤0 = 厂商默认(DeepSeek 是永久)。宿主可接系统参数,每次上传读一次。
46
+ * 低于 `MIN_VENDOR_FILE_TTL_SECONDS`(12h)时按 12h 发。
47
+ */
48
+ ttlSeconds?: () => number | undefined;
49
+ logger?: Logger;
50
+ now?: () => number;
51
+ }
52
+
53
+ export interface UploadInput {
54
+ vendor: string;
55
+ store: VendorFileStoreLike;
56
+ contentHash: string;
57
+ src: string | null;
58
+ data: Uint8Array;
59
+ mediaType: string;
60
+ filename?: string;
61
+ /**
62
+ * 这份字节已有的记录(任一账号,含快到期 / 已到期的):传成功后,落在同一账号上的旧那份
63
+ * 顺手删掉厂商侧副本——上传前不知道会落在哪个账号,所以全给、传完再挑。
64
+ */
65
+ previous?: readonly VendorFileRecord[];
66
+ }
67
+
68
+ /**
69
+ * 厂商文件的取用与上传(agent-base 的 `LlmVendorFileService`)。
70
+ *
71
+ * 与 agent-base 的差别:(1) 存储换成 `VendorFileCache` 端口,进程内 Map 那一层由端口实现
72
+ * 自己决定(库实现可以自带);(2) 键从「凭证 id」换成「厂商 + 账号」,账号由上传结果的
73
+ * `credentialRef` 定——中心替我们选 key,上传前不知道会落在哪;(3) 查询按「任一账号」查,
74
+ * 给挑 pin 用。
75
+ *
76
+ * **失败一律不抛**:上传不成就退到下一档递送(公网 URL / base64),一次厂商侧抖动不该让整轮失败。
77
+ */
78
+ export class VendorFiles {
79
+ private readonly logger: Logger;
80
+ private readonly now: () => number;
81
+ private readonly inflight = new Map<
82
+ string,
83
+ Promise<VendorFileRecord | null>
84
+ >();
85
+
86
+ constructor(private readonly options: VendorFilesOptions) {
87
+ this.logger = options.logger ?? silentLogger('VendorFiles');
88
+ this.now = options.now ?? Date.now;
89
+ }
90
+
91
+ isFresh(record: VendorFileRecord): boolean {
92
+ return (
93
+ record.expiresAt === null ||
94
+ record.expiresAt.getTime() - VENDOR_FILE_EXPIRY_MARGIN_MS > this.now()
95
+ );
96
+ }
97
+
98
+ /** 这些字节在这家厂商任一账号上的记录(含不新鲜的,调用方据 `isFresh` 分)。查不了给空。 */
99
+ async lookup(
100
+ vendor: string,
101
+ contentHashes: readonly string[]
102
+ ): Promise<VendorFileRecord[]> {
103
+ if (contentHashes.length === 0) return [];
104
+ try {
105
+ return await this.options.cache.find(vendor, contentHashes);
106
+ } catch (err) {
107
+ this.logger.warn(`vendor file lookup failed (ignored): ${String(err)}`);
108
+ return [];
109
+ }
110
+ }
111
+
112
+ /** 用上了一条记录:节流地记 `lastUsedAt`。不抛。 */
113
+ touch(record: VendorFileRecord): void {
114
+ const at = this.now();
115
+ if (at - record.lastUsedAt.getTime() < TOUCH_INTERVAL_MS) return;
116
+ record.lastUsedAt = new Date(at);
117
+ this.options.cache.touch(record, new Date(at)).catch((err: unknown) => {
118
+ this.logger.warn(`vendor file touch failed (ignored): ${String(err)}`);
119
+ });
120
+ }
121
+
122
+ /**
123
+ * 上传并记账;拿不到给 null。同一进程内对同一 (厂商, 字节, 期望账号) 的并发调用合流成
124
+ * 一次上传。`expectedAccount` 只用来区分合流键(pin 不同的两次上传不能合)。
125
+ */
126
+ upload(
127
+ input: UploadInput,
128
+ expectedAccount: string | undefined
129
+ ): Promise<VendorFileRecord | null> {
130
+ const key = `${input.vendor}\u0000${expectedAccount ?? ''}\u0000${input.contentHash}`;
131
+ const pending = this.inflight.get(key);
132
+ if (pending) return pending;
133
+ const p = this.uploadSlow(input).finally(() => {
134
+ this.inflight.delete(key);
135
+ });
136
+ this.inflight.set(key, p);
137
+ return p;
138
+ }
139
+
140
+ private async uploadSlow(
141
+ input: UploadInput
142
+ ): Promise<VendorFileRecord | null> {
143
+ const ttl = this.options.ttlSeconds?.();
144
+ let uploaded: Awaited<ReturnType<VendorFileStoreLike['upload']>>;
145
+ try {
146
+ uploaded = await input.store.upload({
147
+ data: input.data,
148
+ mediaType: input.mediaType,
149
+ ...(input.filename !== undefined ? { filename: input.filename } : {}),
150
+ ...(ttl !== undefined && ttl > 0
151
+ ? { ttlSeconds: Math.max(ttl, MIN_VENDOR_FILE_TTL_SECONDS) }
152
+ : {}),
153
+ });
154
+ } catch (err) {
155
+ this.logger.warn(
156
+ `vendor file upload failed for ${input.vendor} (${input.contentHash.slice(0, 12)}): ${String(err)}`
157
+ );
158
+ return null;
159
+ }
160
+ const record: VendorFileRecord = {
161
+ vendor: input.vendor,
162
+ account: uploaded.credentialRef ?? DIRECT_ACCOUNT,
163
+ contentHash: input.contentHash,
164
+ fileId: uploaded.fileId,
165
+ src: input.src,
166
+ mediaType: input.mediaType,
167
+ bytes: input.data.byteLength,
168
+ expiresAt: uploaded.expiresAt,
169
+ lastUsedAt: new Date(this.now()),
170
+ };
171
+ try {
172
+ await this.options.cache.put(record);
173
+ } catch (err) {
174
+ // 传成功了但记账失败:这一轮仍然能用,只是下次要再传一遍。
175
+ this.logger.error(
176
+ `vendor file bookkeeping failed for ${input.contentHash.slice(0, 12)}: ${String(err)}`
177
+ );
178
+ }
179
+ const old = input.previous?.find(
180
+ r => r.account === record.account && r.fileId !== record.fileId
181
+ );
182
+ if (old) {
183
+ // 旧的那份顺手删,删不掉也无妨(它自己会到期)。
184
+ input.store.delete(old.fileId).catch((err: unknown) => {
185
+ this.logger.warn(
186
+ `vendor file ${old.fileId} delete failed (ignored): ${String(err)}`
187
+ );
188
+ });
189
+ }
190
+ return { ...record };
191
+ }
192
+
193
+ /** 厂商说这些 id 不存在了:忘掉。下一次递送重传。不去厂商侧 delete——它已经说没有了。 */
194
+ async forget(
195
+ vendor: string,
196
+ account: string,
197
+ fileIds: readonly string[]
198
+ ): Promise<void> {
199
+ if (fileIds.length === 0) return;
200
+ try {
201
+ await this.options.cache.forget(vendor, account, fileIds);
202
+ this.logger.warn(
203
+ `forgot ${fileIds.length} vendor file(s) rejected by ${vendor} (account ${account})`
204
+ );
205
+ } catch (err) {
206
+ this.logger.warn(`vendor file forget failed (ignored): ${String(err)}`);
207
+ }
208
+ }
209
+
210
+ /** 清掉已经到期的记录(厂商侧早已自动删除)。由宿主定时调用,或用 `startSweep`。 */
211
+ sweepExpired(): Promise<number> {
212
+ return this.options.cache.sweepExpired(new Date(this.now()));
213
+ }
214
+
215
+ /**
216
+ * 定时清到期记录(缺省 5 分钟一次,no-throw,定时器 unref)。返回停止函数。
217
+ * 删的是自己的记录、幂等,多实例各清各的也无妨。
218
+ */
219
+ startSweep(intervalMs = 5 * 60 * 1000): () => void {
220
+ const tick = () => {
221
+ this.sweepExpired().then(
222
+ n => {
223
+ if (n > 0) this.logger.log(`swept ${n} expired vendor file(s)`);
224
+ },
225
+ (err: unknown) => {
226
+ this.logger.warn(`vendor file sweep failed: ${String(err)}`);
227
+ }
228
+ );
229
+ };
230
+ tick();
231
+ const timer = setInterval(tick, intervalMs);
232
+ timer.unref?.();
233
+ return () => clearInterval(timer);
234
+ }
235
+ }