nexus-ai-pro 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/API_STABILITY.md +77 -0
  2. package/CHANGELOG.md +286 -1
  3. package/README.md +19 -9
  4. package/dist/agent/capabilities.d.ts +39 -0
  5. package/dist/agent/capabilities.js +36 -0
  6. package/dist/agent/create-agent.d.ts +61 -11
  7. package/dist/agent/create-agent.js +136 -22
  8. package/dist/agent/index.d.ts +3 -2
  9. package/dist/agent/index.js +1 -1
  10. package/dist/agent/middleware/approval.d.ts +26 -0
  11. package/dist/agent/middleware/approval.js +23 -0
  12. package/dist/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
  13. package/dist/agent/{middleware.js → middleware/basic.js} +3 -5
  14. package/dist/agent/middleware/context-editor.d.ts +52 -0
  15. package/dist/agent/middleware/context-editor.js +66 -0
  16. package/dist/agent/middleware/filesystem.d.ts +44 -0
  17. package/dist/agent/middleware/filesystem.js +83 -0
  18. package/dist/agent/middleware/index.d.ts +19 -0
  19. package/dist/agent/middleware/index.js +9 -0
  20. package/dist/agent/middleware/limits.d.ts +42 -0
  21. package/dist/agent/middleware/limits.js +52 -0
  22. package/dist/agent/middleware/models.d.ts +48 -0
  23. package/dist/agent/middleware/models.js +44 -0
  24. package/dist/agent/middleware/pii.d.ts +67 -0
  25. package/dist/agent/middleware/pii.js +173 -0
  26. package/dist/agent/middleware/retry.d.ts +66 -0
  27. package/dist/agent/middleware/retry.js +59 -0
  28. package/dist/agent/middleware/shared.d.ts +41 -0
  29. package/dist/agent/middleware/shared.js +73 -0
  30. package/dist/agent/middleware/tool-selector.d.ts +64 -0
  31. package/dist/agent/middleware/tool-selector.js +106 -0
  32. package/dist/agent/permissions.d.ts +113 -0
  33. package/dist/agent/permissions.js +180 -0
  34. package/dist/agent/sandbox.d.ts +151 -0
  35. package/dist/agent/sandbox.js +257 -0
  36. package/dist/agent/tool.d.ts +8 -3
  37. package/dist/agent/tool.js +14 -3
  38. package/dist/cache/memory-cache.d.ts +8 -0
  39. package/dist/cache/memory-cache.js +9 -0
  40. package/dist/cache/semantic-cache.d.ts +6 -3
  41. package/dist/cache/semantic-cache.js +5 -2
  42. package/dist/cli/args.js +3 -0
  43. package/dist/cli/db.d.ts +16 -4
  44. package/dist/cli/db.js +169 -22
  45. package/dist/cli/doctor.d.ts +17 -0
  46. package/dist/cli/doctor.js +53 -0
  47. package/dist/cli.js +12 -1
  48. package/dist/core/lifecycle.js +11 -6
  49. package/dist/core/nexus.d.ts +6 -0
  50. package/dist/core/nexus.js +103 -22
  51. package/dist/deep-agent/index.d.ts +109 -0
  52. package/dist/deep-agent/index.js +216 -0
  53. package/dist/doctor/index.d.ts +90 -0
  54. package/dist/doctor/index.js +237 -0
  55. package/dist/graph/functional.d.ts +89 -7
  56. package/dist/graph/functional.js +156 -36
  57. package/dist/graph/graph.d.ts +15 -5
  58. package/dist/graph/graph.js +95 -22
  59. package/dist/graph/lint.d.ts +4 -1
  60. package/dist/graph/lint.js +35 -0
  61. package/dist/images/evals.js +4 -2
  62. package/dist/operations/adapters.d.ts +52 -4
  63. package/dist/operations/adapters.js +194 -7
  64. package/dist/operations/runner.js +1 -0
  65. package/dist/operations/stats.js +8 -1
  66. package/dist/operations/store.d.ts +5 -1
  67. package/dist/operations/store.js +7 -0
  68. package/dist/ops/circuit-breaker.d.ts +52 -0
  69. package/dist/ops/circuit-breaker.js +4 -0
  70. package/dist/ops/circuit-store.d.ts +13 -2
  71. package/dist/ops/circuit-store.js +68 -0
  72. package/dist/ops/circuit-sync.d.ts +17 -0
  73. package/dist/ops/circuit-sync.js +86 -0
  74. package/dist/ops/rate-limiter.d.ts +2 -0
  75. package/dist/ops/rate-limiter.js +2 -0
  76. package/dist/postgres/circuits.d.ts +21 -4
  77. package/dist/postgres/circuits.js +65 -4
  78. package/dist/postgres/client.d.ts +0 -2
  79. package/dist/postgres/client.js +0 -4
  80. package/dist/postgres/evaluate.d.ts +16 -4
  81. package/dist/postgres/evaluate.js +17 -5
  82. package/dist/postgres/fulltext.d.ts +67 -0
  83. package/dist/postgres/fulltext.js +91 -0
  84. package/dist/postgres/index.d.ts +32 -11
  85. package/dist/postgres/index.js +65 -31
  86. package/dist/postgres/migrations.d.ts +60 -0
  87. package/dist/postgres/migrations.js +121 -0
  88. package/dist/postgres/operations.d.ts +14 -3
  89. package/dist/postgres/operations.js +45 -15
  90. package/dist/postgres/prompts.d.ts +11 -2
  91. package/dist/postgres/prompts.js +14 -3
  92. package/dist/postgres/rollups.d.ts +35 -0
  93. package/dist/postgres/rollups.js +129 -0
  94. package/dist/postgres/store.d.ts +11 -2
  95. package/dist/postgres/store.js +14 -3
  96. package/dist/postgres/traces.d.ts +13 -3
  97. package/dist/postgres/traces.js +25 -3
  98. package/dist/postgres/vectors.d.ts +11 -2
  99. package/dist/postgres/vectors.js +14 -3
  100. package/dist/rag/elasticsearch.d.ts +71 -0
  101. package/dist/rag/elasticsearch.js +130 -0
  102. package/dist/rag/pipeline.d.ts +140 -0
  103. package/dist/rag/pipeline.js +187 -0
  104. package/dist/rag/rerankers.d.ts +92 -0
  105. package/dist/rag/rerankers.js +97 -0
  106. package/dist/rag/retrievers.d.ts +12 -1
  107. package/dist/router/auto-router.d.ts +34 -7
  108. package/dist/router/auto-router.js +201 -68
  109. package/dist/server/assistant.d.ts +9 -1
  110. package/dist/server/assistant.js +10 -0
  111. package/dist/server/auth.d.ts +250 -0
  112. package/dist/server/auth.js +392 -0
  113. package/dist/server/errors.d.ts +12 -0
  114. package/dist/server/errors.js +7 -0
  115. package/dist/server/index.d.ts +1 -1
  116. package/dist/server/index.js +1 -1
  117. package/dist/server/runs.d.ts +24 -1
  118. package/dist/server/runs.js +90 -14
  119. package/dist/sqlite/index.d.ts +16 -3
  120. package/dist/sqlite/index.js +16 -3
  121. package/dist/sqlite/migrations.d.ts +26 -0
  122. package/dist/sqlite/migrations.js +63 -0
  123. package/dist/sqlite/operations.d.ts +14 -3
  124. package/dist/sqlite/operations.js +42 -15
  125. package/dist/sqlite/store.d.ts +9 -2
  126. package/dist/sqlite/store.js +13 -3
  127. package/dist/sqlite/vectors.d.ts +11 -2
  128. package/dist/sqlite/vectors.js +13 -2
  129. package/dist/store/memory.d.ts +10 -0
  130. package/dist/store/memory.js +10 -0
  131. package/dist/store/tenant.d.ts +12 -0
  132. package/dist/store/tenant.js +29 -0
  133. package/dist/tenancy/index.d.ts +113 -0
  134. package/dist/tenancy/index.js +233 -0
  135. package/dist/tracing/instrument.js +2 -0
  136. package/dist/types/config.d.ts +24 -2
  137. package/dist/types/graph.d.ts +49 -1
  138. package/dist/types/messages.d.ts +41 -2
  139. package/dist/types/operations.d.ts +6 -0
  140. package/dist/types/principal.d.ts +21 -0
  141. package/dist/types/principal.js +1 -0
  142. package/dist/types/response.d.ts +20 -0
  143. package/dist/types/server.d.ts +12 -10
  144. package/dist/utils/schema-migrations.d.ts +97 -0
  145. package/dist/utils/schema-migrations.js +91 -0
  146. package/dist/utils/signals.d.ts +17 -0
  147. package/dist/utils/signals.js +26 -0
  148. package/dist/utils/tenant.d.ts +25 -0
  149. package/dist/utils/tenant.js +30 -0
  150. package/dist/version.d.ts +1 -1
  151. package/dist/version.js +1 -1
  152. package/dist-cjs/agent/capabilities.d.ts +39 -0
  153. package/dist-cjs/agent/capabilities.js +41 -0
  154. package/dist-cjs/agent/create-agent.d.ts +61 -11
  155. package/dist-cjs/agent/create-agent.js +136 -22
  156. package/dist-cjs/agent/index.d.ts +3 -2
  157. package/dist-cjs/agent/index.js +4 -4
  158. package/dist-cjs/agent/middleware/approval.d.ts +26 -0
  159. package/dist-cjs/agent/middleware/approval.js +26 -0
  160. package/dist-cjs/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
  161. package/dist-cjs/agent/{middleware.js → middleware/basic.js} +3 -5
  162. package/dist-cjs/agent/middleware/context-editor.d.ts +52 -0
  163. package/dist-cjs/agent/middleware/context-editor.js +70 -0
  164. package/dist-cjs/agent/middleware/filesystem.d.ts +44 -0
  165. package/dist-cjs/agent/middleware/filesystem.js +86 -0
  166. package/dist-cjs/agent/middleware/index.d.ts +19 -0
  167. package/dist-cjs/agent/middleware/index.js +28 -0
  168. package/dist-cjs/agent/middleware/limits.d.ts +42 -0
  169. package/dist-cjs/agent/middleware/limits.js +57 -0
  170. package/dist-cjs/agent/middleware/models.d.ts +48 -0
  171. package/dist-cjs/agent/middleware/models.js +48 -0
  172. package/dist-cjs/agent/middleware/pii.d.ts +67 -0
  173. package/dist-cjs/agent/middleware/pii.js +178 -0
  174. package/dist-cjs/agent/middleware/retry.d.ts +66 -0
  175. package/dist-cjs/agent/middleware/retry.js +63 -0
  176. package/dist-cjs/agent/middleware/shared.d.ts +41 -0
  177. package/dist-cjs/agent/middleware/shared.js +83 -0
  178. package/dist-cjs/agent/middleware/tool-selector.d.ts +64 -0
  179. package/dist-cjs/agent/middleware/tool-selector.js +109 -0
  180. package/dist-cjs/agent/permissions.d.ts +113 -0
  181. package/dist-cjs/agent/permissions.js +189 -0
  182. package/dist-cjs/agent/sandbox.d.ts +151 -0
  183. package/dist-cjs/agent/sandbox.js +297 -0
  184. package/dist-cjs/agent/tool.d.ts +8 -3
  185. package/dist-cjs/agent/tool.js +14 -3
  186. package/dist-cjs/cache/memory-cache.d.ts +8 -0
  187. package/dist-cjs/cache/memory-cache.js +9 -0
  188. package/dist-cjs/cache/semantic-cache.d.ts +6 -3
  189. package/dist-cjs/cache/semantic-cache.js +5 -2
  190. package/dist-cjs/cli/args.js +3 -0
  191. package/dist-cjs/cli/db.d.ts +16 -4
  192. package/dist-cjs/cli/db.js +200 -20
  193. package/dist-cjs/cli/doctor.d.ts +17 -0
  194. package/dist-cjs/cli/doctor.js +56 -0
  195. package/dist-cjs/core/lifecycle.js +11 -6
  196. package/dist-cjs/core/nexus.d.ts +6 -0
  197. package/dist-cjs/core/nexus.js +103 -22
  198. package/dist-cjs/deep-agent/index.d.ts +109 -0
  199. package/dist-cjs/deep-agent/index.js +219 -0
  200. package/dist-cjs/doctor/index.d.ts +90 -0
  201. package/dist-cjs/doctor/index.js +276 -0
  202. package/dist-cjs/graph/functional.d.ts +89 -7
  203. package/dist-cjs/graph/functional.js +155 -35
  204. package/dist-cjs/graph/graph.d.ts +15 -5
  205. package/dist-cjs/graph/graph.js +95 -22
  206. package/dist-cjs/graph/lint.d.ts +4 -1
  207. package/dist-cjs/graph/lint.js +35 -0
  208. package/dist-cjs/images/evals.js +4 -2
  209. package/dist-cjs/operations/adapters.d.ts +52 -4
  210. package/dist-cjs/operations/adapters.js +193 -6
  211. package/dist-cjs/operations/runner.js +1 -0
  212. package/dist-cjs/operations/stats.js +8 -1
  213. package/dist-cjs/operations/store.d.ts +5 -1
  214. package/dist-cjs/operations/store.js +7 -0
  215. package/dist-cjs/ops/circuit-breaker.d.ts +52 -0
  216. package/dist-cjs/ops/circuit-breaker.js +4 -0
  217. package/dist-cjs/ops/circuit-store.d.ts +13 -2
  218. package/dist-cjs/ops/circuit-store.js +68 -0
  219. package/dist-cjs/ops/circuit-sync.d.ts +17 -0
  220. package/dist-cjs/ops/circuit-sync.js +87 -0
  221. package/dist-cjs/ops/rate-limiter.d.ts +2 -0
  222. package/dist-cjs/ops/rate-limiter.js +2 -0
  223. package/dist-cjs/postgres/circuits.d.ts +21 -4
  224. package/dist-cjs/postgres/circuits.js +98 -3
  225. package/dist-cjs/postgres/client.d.ts +0 -2
  226. package/dist-cjs/postgres/client.js +0 -5
  227. package/dist-cjs/postgres/evaluate.d.ts +16 -4
  228. package/dist-cjs/postgres/evaluate.js +50 -4
  229. package/dist-cjs/postgres/fulltext.d.ts +67 -0
  230. package/dist-cjs/postgres/fulltext.js +129 -0
  231. package/dist-cjs/postgres/index.d.ts +32 -11
  232. package/dist-cjs/postgres/index.js +69 -20
  233. package/dist-cjs/postgres/migrations.d.ts +60 -0
  234. package/dist-cjs/postgres/migrations.js +129 -0
  235. package/dist-cjs/postgres/operations.d.ts +14 -3
  236. package/dist-cjs/postgres/operations.js +78 -14
  237. package/dist-cjs/postgres/prompts.d.ts +11 -2
  238. package/dist-cjs/postgres/prompts.js +47 -2
  239. package/dist-cjs/postgres/rollups.d.ts +35 -0
  240. package/dist-cjs/postgres/rollups.js +168 -0
  241. package/dist-cjs/postgres/store.d.ts +11 -2
  242. package/dist-cjs/postgres/store.js +47 -2
  243. package/dist-cjs/postgres/traces.d.ts +13 -3
  244. package/dist-cjs/postgres/traces.js +58 -2
  245. package/dist-cjs/postgres/vectors.d.ts +11 -2
  246. package/dist-cjs/postgres/vectors.js +47 -2
  247. package/dist-cjs/rag/elasticsearch.d.ts +71 -0
  248. package/dist-cjs/rag/elasticsearch.js +135 -0
  249. package/dist-cjs/rag/pipeline.d.ts +140 -0
  250. package/dist-cjs/rag/pipeline.js +191 -0
  251. package/dist-cjs/rag/rerankers.d.ts +92 -0
  252. package/dist-cjs/rag/rerankers.js +107 -0
  253. package/dist-cjs/rag/retrievers.d.ts +12 -1
  254. package/dist-cjs/router/auto-router.d.ts +34 -7
  255. package/dist-cjs/router/auto-router.js +201 -68
  256. package/dist-cjs/server/assistant.d.ts +9 -1
  257. package/dist-cjs/server/assistant.js +10 -0
  258. package/dist-cjs/server/auth.d.ts +250 -0
  259. package/dist-cjs/server/auth.js +402 -0
  260. package/dist-cjs/server/errors.d.ts +12 -0
  261. package/dist-cjs/server/errors.js +9 -1
  262. package/dist-cjs/server/index.d.ts +1 -1
  263. package/dist-cjs/server/index.js +2 -1
  264. package/dist-cjs/server/runs.d.ts +24 -1
  265. package/dist-cjs/server/runs.js +89 -13
  266. package/dist-cjs/sqlite/index.d.ts +16 -3
  267. package/dist-cjs/sqlite/index.js +31 -10
  268. package/dist-cjs/sqlite/migrations.d.ts +26 -0
  269. package/dist-cjs/sqlite/migrations.js +71 -0
  270. package/dist-cjs/sqlite/operations.d.ts +14 -3
  271. package/dist-cjs/sqlite/operations.js +76 -15
  272. package/dist-cjs/sqlite/store.d.ts +9 -2
  273. package/dist-cjs/sqlite/store.js +47 -3
  274. package/dist-cjs/sqlite/vectors.d.ts +11 -2
  275. package/dist-cjs/sqlite/vectors.js +47 -2
  276. package/dist-cjs/store/memory.d.ts +10 -0
  277. package/dist-cjs/store/memory.js +10 -0
  278. package/dist-cjs/store/tenant.d.ts +12 -0
  279. package/dist-cjs/store/tenant.js +33 -0
  280. package/dist-cjs/tenancy/index.d.ts +113 -0
  281. package/dist-cjs/tenancy/index.js +250 -0
  282. package/dist-cjs/tracing/instrument.js +2 -0
  283. package/dist-cjs/types/config.d.ts +24 -2
  284. package/dist-cjs/types/graph.d.ts +49 -1
  285. package/dist-cjs/types/messages.d.ts +41 -2
  286. package/dist-cjs/types/operations.d.ts +6 -0
  287. package/dist-cjs/types/principal.d.ts +21 -0
  288. package/dist-cjs/types/principal.js +2 -0
  289. package/dist-cjs/types/response.d.ts +20 -0
  290. package/dist-cjs/types/server.d.ts +12 -10
  291. package/dist-cjs/utils/schema-migrations.d.ts +97 -0
  292. package/dist-cjs/utils/schema-migrations.js +102 -0
  293. package/dist-cjs/utils/signals.d.ts +17 -0
  294. package/dist-cjs/utils/signals.js +29 -0
  295. package/dist-cjs/utils/tenant.d.ts +25 -0
  296. package/dist-cjs/utils/tenant.js +37 -0
  297. package/dist-cjs/version.d.ts +1 -1
  298. package/dist-cjs/version.js +1 -1
  299. package/llms.txt +6 -4
  300. package/package.json +188 -2
@@ -1,14 +1,19 @@
1
1
  import type { OperationLifecycleLike } from '../types/lifecycle.js';
2
+ import type { Principal } from '../types/principal.js';
2
3
  import { GraphError } from './errors.js';
3
- import type { GraphCheckpoint, GraphCheckpointer, GraphDescription, GraphEvent, GraphStatus, InterruptRequest, PendingInterrupt, RetryPolicy } from '../types/graph.js';
4
+ import type { GraphCheckpoint, GraphCheckpointer, GraphDescription, GraphEvent, GraphStatus, InterruptRequest, PendingInterrupt, RetryPolicy, RunControlLike } from '../types/graph.js';
4
5
  import type { Store } from '../types/store.js';
5
6
  /** What a workflow checkpoint holds: the input, every completed step's result, and the output. */
6
7
  export interface WorkflowState<I = unknown, O = unknown> {
7
8
  /** The input the workflow was started with. */
8
9
  input: I;
9
- /** Each completed step's result, keyed by step name — `name#2` for the second call with a name. */
10
+ /**
11
+ * Each completed step's result, keyed by step name — `name#2` for the second call with a name. A
12
+ * step whose `onError` supplied its result after its retries ran out also records `recovered`.
13
+ */
10
14
  steps: Record<string, {
11
15
  value?: unknown;
16
+ recovered?: StepRecovery;
12
17
  }>;
13
18
  /** The workflow's return value, once it has completed. */
14
19
  output?: O;
@@ -23,21 +28,70 @@ export interface StepContext {
23
28
  readonly attempt: number;
24
29
  /** Aborted when the run is cancelled or the step exceeds its timeout. */
25
30
  readonly signal: AbortSignal;
31
+ /**
32
+ * Shows the step is still making progress, which refreshes its idle timeout. For long work that
33
+ * sends nothing else, such as a large upload or a slow tool call.
34
+ */
35
+ heartbeat(): void;
36
+ }
37
+ /** Limits on one attempt of a step: its whole run, and the time it may go without progress. */
38
+ export interface StepTimeout {
39
+ /** Fails the attempt when it runs longer than this, in milliseconds. */
40
+ runMs?: number;
41
+ /**
42
+ * Fails the attempt when this long passes without `context.heartbeat()`, in milliseconds, which
43
+ * tells a hung step from a slow one that is working.
44
+ */
45
+ idleMs?: number;
46
+ }
47
+ /** What a step's `onError` receives once its retries have run out. */
48
+ export interface StepFailure {
49
+ /** The step's name. */
50
+ name: string;
51
+ /** The key its result is recorded under. */
52
+ key: string;
53
+ /** Attempts made. */
54
+ attempts: number;
55
+ /** The last attempt's error. */
56
+ error: unknown;
57
+ }
58
+ /** How a step's result was recovered, as recorded in the workflow state. */
59
+ export interface StepRecovery {
60
+ /** The error that exhausted the step's retries. */
61
+ error: {
62
+ name: string;
63
+ message: string;
64
+ };
65
+ /** Attempts made before `onError` decided. */
66
+ attempts: number;
26
67
  }
27
68
  /** How one step runs. */
28
- export interface StepOptions {
69
+ export interface StepOptions<T = unknown> {
29
70
  /** Retries a failing attempt, overriding the workflow's default policy. */
30
71
  retry?: RetryPolicy;
31
72
  /** Aborts the step's signal and fails the attempt when it runs longer than this, in milliseconds. */
32
73
  timeoutMs?: number;
74
+ /** Run and idle limits on each attempt. `runMs` is `timeoutMs` by another name; `timeoutMs` wins when both are set. */
75
+ timeout?: StepTimeout;
76
+ /**
77
+ * Decides what happens once the step's retries run out. What it returns becomes the step's
78
+ * result, recorded with `recovered`, so a resumed run does not run the step again — a refund after
79
+ * a charge that kept failing, say. Throwing fails the step with that error. A cancelled run, a
80
+ * question, or a drain is never passed to it.
81
+ */
82
+ onError?: (failure: StepFailure) => T | Promise<T>;
33
83
  }
34
84
  /** What a workflow function receives besides its input. */
35
85
  export interface WorkflowContext {
36
86
  /** The thread this run belongs to. */
37
87
  readonly threadId: string;
88
+ /** The tenant the run is for, when it has one. */
89
+ readonly tenantId?: string;
90
+ /** Who the run is for, when it was started with a principal: the caller the server authenticated. */
91
+ readonly principal?: Readonly<Principal>;
38
92
  /** Aborted when the run is cancelled. */
39
93
  readonly signal: AbortSignal;
40
- /** Long-term memory, when the workflow was created with a store. */
94
+ /** Long-term memory, when the workflow was created with a store. In a tenant's run, the tenant's view of it. */
41
95
  readonly store?: Store;
42
96
  /**
43
97
  * Runs a piece of work once per thread and records its result.
@@ -46,7 +100,7 @@ export interface WorkflowContext {
46
100
  * running. Name steps so the same call has the same name every time; a name used more than once
47
101
  * is numbered in call order. The result must survive JSON serialization.
48
102
  */
49
- step<T>(name: string, run: (context: StepContext) => Promise<T> | T, options?: StepOptions): Promise<T>;
103
+ step<T>(name: string, run: (context: StepContext) => Promise<T> | T, options?: StepOptions<T>): Promise<T>;
50
104
  /**
51
105
  * Suspends the workflow until a person answers.
52
106
  *
@@ -73,6 +127,11 @@ export interface WorkflowOptions {
73
127
  store?: Store;
74
128
  /** Default retry policy for every step. A step's own policy wins. Defaults to one attempt. */
75
129
  retry?: RetryPolicy;
130
+ /**
131
+ * Defaults for every step: its retries, its run and idle timeouts, and its `onError`. A step's own
132
+ * options win, field by field.
133
+ */
134
+ stepDefaults?: Pick<StepOptions, 'retry' | 'timeout' | 'onError'>;
76
135
  /** Steps that may run at once, for a workflow that starts many with `Promise.all`. Defaults to 16. */
77
136
  maxConcurrency?: number;
78
137
  /**
@@ -93,6 +152,25 @@ export interface WorkflowRunOptions {
93
152
  metadata?: Record<string, unknown>;
94
153
  /** Receives each step starting, retrying, and finishing, each checkpoint, and custom events. */
95
154
  onEvent?: (event: GraphEvent) => void;
155
+ /**
156
+ * Lets the caller stop the run between steps: pass a `RunControl` and call `drain()`. Steps in
157
+ * flight finish and are recorded, no new step starts, and the run ends with `GraphDrainedError`;
158
+ * `continue()` carries it on, on this worker or another. The agent server drains a workflow it
159
+ * serves this way.
160
+ */
161
+ control?: RunControlLike;
162
+ /**
163
+ * The tenant the run is for. Recorded on every checkpoint as `metadata.tenantId`, read as
164
+ * `context.tenantId`, and scoping `context.store`; another tenant's thread is not found. A run
165
+ * without one carries on the thread's.
166
+ */
167
+ tenantId?: string;
168
+ /**
169
+ * Who the run is for, read as `context.principal`. Its `tenantId` is the run's tenant when
170
+ * `tenantId` is not given, and a run whose two tenants differ is refused; its `userId` is recorded
171
+ * on every checkpoint as `metadata.userId`.
172
+ */
173
+ principal?: Principal;
96
174
  }
97
175
  /** One event of a streamed workflow run. */
98
176
  export type WorkflowEvent<O = unknown> = {
@@ -152,17 +230,21 @@ export interface WorkflowResult<O = unknown> {
152
230
  /** Every pending question. */
153
231
  interrupts?: PendingInterrupt[];
154
232
  }
155
- /** Raised when a step outlives its `timeoutMs`. */
233
+ /** Raised when a step outlives its run timeout, or goes longer than its idle timeout without progress. */
156
234
  export declare class WorkflowStepTimeoutError extends GraphError {
157
235
  /** The step. */
158
236
  readonly stepName: string;
159
237
  /** The limit it exceeded, in milliseconds. */
160
238
  readonly timeoutMs: number;
239
+ /** Which limit: `run` for the whole attempt, `idle` for the time without progress. */
240
+ readonly kind: 'run' | 'idle';
161
241
  constructor(
162
242
  /** The step. */
163
243
  stepName: string,
164
244
  /** The limit it exceeded, in milliseconds. */
165
- timeoutMs: number);
245
+ timeoutMs: number,
246
+ /** Which limit: `run` for the whole attempt, `idle` for the time without progress. */
247
+ kind?: 'run' | 'idle');
166
248
  }
167
249
  /**
168
250
  * A durable workflow: a function whose steps are checkpointed, so it survives interrupts, restarts,
@@ -1,17 +1,28 @@
1
1
  import { MemoryGraphCheckpointer } from './checkpointer.js';
2
2
  import { withLifecycle } from '../lifecycle/run.js';
3
3
  import { migrateCheckpoint, toCheckpoint } from './checkpoint-migration.js';
4
- import { GraphError, GraphNotInterruptedError, GraphThreadNotFoundError } from './errors.js';
4
+ import { tenantStore } from '../store/tenant.js';
5
+ import { withPrincipal } from '../utils/tenant.js';
6
+ import { GraphDrainedError, GraphError, GraphNotInterruptedError, GraphThreadNotFoundError } from './errors.js';
5
7
  export class WorkflowStepTimeoutError extends GraphError {
6
8
  stepName;
7
9
  timeoutMs;
8
- constructor(stepName, timeoutMs) {
9
- super(`Step "${stepName}" exceeded its ${timeoutMs} ms timeout`, 'WORKFLOW_STEP_TIMEOUT');
10
+ kind;
11
+ constructor(stepName, timeoutMs, kind = 'run') {
12
+ super(kind === 'idle'
13
+ ? `Step "${stepName}" made no progress for ${timeoutMs} ms`
14
+ : `Step "${stepName}" exceeded its ${timeoutMs} ms timeout`, 'WORKFLOW_STEP_TIMEOUT');
10
15
  this.stepName = stepName;
11
16
  this.timeoutMs = timeoutMs;
17
+ this.kind = kind;
12
18
  this.name = 'WorkflowStepTimeoutError';
13
19
  }
14
20
  }
21
+ class WorkflowDraining extends Error {
22
+ constructor() {
23
+ super('The workflow is draining');
24
+ }
25
+ }
15
26
  class WorkflowSuspended extends Error {
16
27
  pending;
17
28
  constructor(pending) {
@@ -40,10 +51,17 @@ export class Workflow {
40
51
  }
41
52
  stream(input, runOptions = {}) {
42
53
  const threadId = runOptions.threadId ?? generatedId();
43
- return this.execute(threadId, runOptions, async () => ({
44
- ...this.fresh(threadId, input),
45
- step: (await this.latest(threadId))?.step ?? 0,
46
- }));
54
+ return this.execute(threadId, runOptions, async () => {
55
+ const latest = await this.latest(threadId);
56
+ const tenantId = runOptions.tenantId ?? runOptions.principal?.tenantId;
57
+ if (latest && tenantId !== undefined) {
58
+ const owner = latest.metadata?.tenantId;
59
+ if (typeof owner === 'string' && owner !== tenantId) {
60
+ throw new GraphError(`Thread "${threadId}" cannot be used by this tenant; choose another id`, 'GRAPH_VALIDATION_ERROR');
61
+ }
62
+ }
63
+ return { ...this.fresh(threadId, input), step: latest?.step ?? 0 };
64
+ });
47
65
  }
48
66
  resume(threadId, value, runOptions = {}) {
49
67
  return this.execute(threadId, runOptions, async () => {
@@ -132,16 +150,40 @@ export class Workflow {
132
150
  }
133
151
  execute(threadId, runOptions, start) {
134
152
  const queue = new EventQueue();
135
- void withLifecycle(this.options.lifecycle, { family: 'graph', operation: `workflow.${this.name}`, metadata: { threadId, graph: this.name } }, runOptions.signal ? { signal: runOptions.signal } : {}, () => this.run(threadId, runOptions, start, queue)).then(() => queue.close(), (error) => queue.fail(error));
153
+ let options;
154
+ try {
155
+ options = withPrincipal(runOptions);
156
+ }
157
+ catch (error) {
158
+ queue.fail(error);
159
+ return queue;
160
+ }
161
+ const metadata = {
162
+ threadId,
163
+ graph: this.name,
164
+ ...(options.tenantId !== undefined ? { tenantId: options.tenantId } : {}),
165
+ ...(options.principal?.userId !== undefined ? { userId: options.principal.userId } : {}),
166
+ };
167
+ void withLifecycle(this.options.lifecycle, { family: 'graph', operation: `workflow.${this.name}`, metadata }, options.signal ? { signal: options.signal } : {}, () => this.run(threadId, options, start, queue)).then(() => queue.close(), (error) => queue.fail(error));
136
168
  return queue;
137
169
  }
138
170
  async run(threadId, runOptions, start, queue) {
139
171
  const initial = await start();
172
+ const owner = typeof initial.metadata?.tenantId === 'string' ? initial.metadata.tenantId : undefined;
173
+ if (runOptions.tenantId !== undefined && owner !== undefined && owner !== runOptions.tenantId) {
174
+ throw new GraphThreadNotFoundError(threadId);
175
+ }
176
+ const tenantId = runOptions.tenantId ?? owner;
140
177
  const checkpointer = this.checkpointer();
141
178
  const state = structuredClone(initial.state);
142
179
  state.steps ??= {};
143
180
  const resolved = { ...initial.resolved };
144
- const metadata = { ...initial.metadata, graph: this.name, ...runOptions.metadata };
181
+ const metadata = {
182
+ ...initial.metadata,
183
+ graph: this.name,
184
+ ...runOptions.metadata,
185
+ ...(tenantId === undefined ? {} : { tenantId }),
186
+ };
145
187
  let stepNumber = initial.step;
146
188
  let writes = Promise.resolve();
147
189
  const persist = (checkpoint) => {
@@ -171,11 +213,17 @@ export class Workflow {
171
213
  const inFlight = new Set();
172
214
  const suspended = [];
173
215
  let interruptIndex = 0;
216
+ let drained = false;
174
217
  const limiter = new Limiter(this.options.maxConcurrency ?? 16);
218
+ const defaults = this.options.stepDefaults ?? {};
175
219
  const context = {
176
220
  threadId,
221
+ ...(tenantId === undefined ? {} : { tenantId }),
222
+ ...(runOptions.principal ? { principal: runOptions.principal } : {}),
177
223
  signal: controller.signal,
178
- ...(this.options.store ? { store: this.options.store } : {}),
224
+ ...(this.options.store
225
+ ? { store: tenantId === undefined ? this.options.store : tenantStore(this.options.store, tenantId) }
226
+ : {}),
179
227
  step: (name, run, options = {}) => {
180
228
  const count = (counts.get(name) ?? 0) + 1;
181
229
  counts.set(name, count);
@@ -185,10 +233,22 @@ export class Workflow {
185
233
  queue.push({ type: 'step', step: stepNumber, name, key, cached: true, state: snapshot(), status: 'running' });
186
234
  return Promise.resolve(recorded.value);
187
235
  }
236
+ if (runOptions.control?.draining) {
237
+ drained = true;
238
+ return Promise.reject(new WorkflowDraining());
239
+ }
240
+ const merged = {
241
+ ...defaults,
242
+ ...options,
243
+ timeout: { ...defaults.timeout, ...options.timeout },
244
+ };
188
245
  const work = limiter
189
- .run(() => this.runStep(name, key, run, { ...this.options.retry, ...options.retry }, options, controller.signal, runOptions, stepNumber))
190
- .then(async (value) => {
191
- state.steps[key] = value === undefined ? {} : { value: structuredClone(value) };
246
+ .run(() => this.runStep(name, key, run, { ...this.options.retry, ...defaults.retry, ...options.retry }, merged, controller.signal, runOptions, stepNumber))
247
+ .then(async ({ value, recovered }) => {
248
+ state.steps[key] = {
249
+ ...(value === undefined ? {} : { value: structuredClone(value) }),
250
+ ...(recovered ? { recovered } : {}),
251
+ };
192
252
  await persist({ state: state, next: [this.name], status: 'running' });
193
253
  queue.push({
194
254
  type: 'step',
@@ -262,6 +322,18 @@ export class Workflow {
262
322
  queue.push({ type: 'interrupt', step: stepNumber, state: snapshot(), status: 'interrupted' });
263
323
  return;
264
324
  }
325
+ if (drained && outcome.kind === 'failed') {
326
+ const reason = runOptions.control?.reason;
327
+ await persist({
328
+ state: state,
329
+ next: [this.name],
330
+ status: 'interrupted',
331
+ resolved,
332
+ drained: { ...(reason ? { reason } : {}), at: this.now().toISOString() },
333
+ });
334
+ await writes;
335
+ throw new GraphDrainedError(threadId, stepNumber, reason);
336
+ }
265
337
  if (outcome.kind === 'failed') {
266
338
  const error = outcome.error;
267
339
  await persist({
@@ -284,41 +356,87 @@ export class Workflow {
284
356
  }
285
357
  async runStep(name, key, run, retry, options, signal, runOptions, step) {
286
358
  const policy = { ...DEFAULT_RETRY, ...retry };
359
+ const runMs = options.timeoutMs ?? options.timeout?.runMs;
360
+ const idleMs = options.timeout?.idleMs;
287
361
  for (let attempt = 1;; attempt += 1) {
288
362
  notify(runOptions, { type: 'task_start', step, taskId: key, node: name, attempt });
289
363
  const stepController = new AbortController();
290
364
  const forward = () => stepController.abort(signal.reason);
291
365
  signal.addEventListener('abort', forward, { once: true });
292
- let timer;
293
- let timedOut = false;
366
+ let runTimer;
367
+ let idleTimer;
368
+ let timedOut;
369
+ let expire = () => undefined;
370
+ const limits = runMs === undefined && idleMs === undefined
371
+ ? undefined
372
+ : new Promise((_, reject) => {
373
+ expire = (error) => {
374
+ if (timedOut)
375
+ return;
376
+ timedOut = error;
377
+ reject(error);
378
+ stepController.abort();
379
+ };
380
+ });
381
+ limits?.catch(() => undefined);
382
+ if (runMs !== undefined)
383
+ runTimer = setTimeout(() => expire(new WorkflowStepTimeoutError(name, runMs)), runMs);
384
+ const armIdle = () => {
385
+ if (idleMs === undefined || timedOut)
386
+ return;
387
+ if (idleTimer)
388
+ clearTimeout(idleTimer);
389
+ idleTimer = setTimeout(() => expire(new WorkflowStepTimeoutError(name, idleMs, 'idle')), idleMs);
390
+ };
391
+ armIdle();
294
392
  try {
295
- const work = Promise.resolve(run({ name, key, attempt, signal: stepController.signal }));
296
- const value = options.timeoutMs === undefined
297
- ? await work
298
- : await Promise.race([
299
- work,
300
- new Promise((_, reject) => {
301
- timer = setTimeout(() => {
302
- timedOut = true;
303
- reject(new WorkflowStepTimeoutError(name, options.timeoutMs));
304
- stepController.abort();
305
- }, options.timeoutMs);
306
- }),
307
- ]);
393
+ const work = Promise.resolve(run({ name, key, attempt, signal: stepController.signal, heartbeat: armIdle }));
394
+ const value = limits ? await Promise.race([work, limits]) : await work;
308
395
  if (timedOut)
309
- throw new WorkflowStepTimeoutError(name, options.timeoutMs);
396
+ throw timedOut;
310
397
  notify(runOptions, { type: 'task_end', step, taskId: key, node: name, update: value });
311
- return value;
398
+ return { value };
312
399
  }
313
400
  catch (error) {
314
401
  if (signal.aborted || error instanceof WorkflowSuspended)
315
402
  throw error;
316
- const failure = timedOut ? new WorkflowStepTimeoutError(name, options.timeoutMs) : error;
403
+ const failure = timedOut ?? error;
317
404
  const retryable = policy.retryOn
318
405
  ? policy.retryOn(failure, attempt)
319
- : !(failure instanceof GraphError) || timedOut;
320
- if (attempt >= Math.max(1, policy.maxAttempts) || !retryable)
321
- throw failure;
406
+ : !(failure instanceof GraphError) || timedOut !== undefined;
407
+ if (attempt >= Math.max(1, policy.maxAttempts) || !retryable) {
408
+ if (!options.onError)
409
+ throw failure;
410
+ const failed = (recovered) => notify(runOptions, {
411
+ type: 'task_failed',
412
+ step,
413
+ taskId: key,
414
+ node: name,
415
+ attempts: attempt,
416
+ error: failure instanceof Error ? failure.message : String(failure),
417
+ recovered,
418
+ });
419
+ let value;
420
+ try {
421
+ value = await options.onError({ name, key, attempts: attempt, error: failure });
422
+ }
423
+ catch (decision) {
424
+ failed(false);
425
+ throw decision;
426
+ }
427
+ failed(true);
428
+ notify(runOptions, { type: 'task_end', step, taskId: key, node: name, update: value });
429
+ return {
430
+ value,
431
+ recovered: {
432
+ error: {
433
+ name: failure instanceof Error ? failure.name : 'Error',
434
+ message: failure instanceof Error ? failure.message : String(failure),
435
+ },
436
+ attempts: attempt,
437
+ },
438
+ };
439
+ }
322
440
  notify(runOptions, {
323
441
  type: 'task_retry',
324
442
  step,
@@ -330,8 +448,10 @@ export class Workflow {
330
448
  await sleep(backoff(policy, attempt), signal);
331
449
  }
332
450
  finally {
333
- if (timer)
334
- clearTimeout(timer);
451
+ if (runTimer)
452
+ clearTimeout(runTimer);
453
+ if (idleTimer)
454
+ clearTimeout(idleTimer);
335
455
  signal.removeEventListener('abort', forward);
336
456
  }
337
457
  }
@@ -111,10 +111,17 @@ export declare class CompiledGraph<S extends ChannelSchema, I extends keyof S =
111
111
  * once the rewound checkpoint is written.
112
112
  */
113
113
  resumeFrom(threadId: string, step: number, runOptions?: GraphRunOptions): AsyncIterable<GraphStepEvent<S>>;
114
- /** Latest checkpoint, or the one at `step`. */
115
- state(threadId: string, step?: number): Promise<GraphCheckpoint<S> | undefined>;
116
- /** Checkpoints for a thread, newest first. */
117
- history(threadId: string, limit?: number): Promise<Array<GraphCheckpoint<S>>>;
114
+ /**
115
+ * Latest checkpoint, or the one at `step`. With a `tenantId`, a thread another tenant owns reads
116
+ * as absent.
117
+ */
118
+ state(threadId: string, step?: number, options?: {
119
+ tenantId?: string;
120
+ }): Promise<GraphCheckpoint<S> | undefined>;
121
+ /** Checkpoints for a thread, newest first. With a `tenantId`, another tenant's thread has none. */
122
+ history(threadId: string, limit?: number, options?: {
123
+ tenantId?: string;
124
+ }): Promise<Array<GraphCheckpoint<S>>>;
118
125
  /**
119
126
  * Wraps this graph as a node in another graph.
120
127
  *
@@ -143,17 +150,20 @@ export declare class CompiledGraph<S extends ChannelSchema, I extends keyof S =
143
150
  */
144
151
  updateState(threadId: string, update: StateUpdate<S>, options?: {
145
152
  asNode?: string;
153
+ tenantId?: string;
146
154
  }): Promise<GraphCheckpoint<S>>;
147
155
  /**
148
156
  * Copies a thread's history up to `step` into a new thread, and returns the new thread's id.
149
157
  *
150
158
  * Rewinding with `resumeFrom()` replaces the original timeline. A fork keeps it: both threads stay
151
159
  * readable and runnable, so two answers to the same question can be compared side by side. Every
152
- * copied checkpoint records `metadata.forkedFrom`.
160
+ * copied checkpoint records `metadata.forkedFrom`, and keeps the tenant of the thread it came from.
161
+ * With a `tenantId`, another tenant's thread is not found.
153
162
  */
154
163
  fork(threadId: string, options?: {
155
164
  step?: number;
156
165
  threadId?: string;
166
+ tenantId?: string;
157
167
  }): Promise<string>;
158
168
  /**
159
169
  * Runs the supersteps, as one operation of the compiled lifecycle when there is one. The run is