@kortyx/hooks 0.17.1 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.19.0](https://github.com/kortyx-io/kortyx/compare/hooks-v0.18.0...hooks-v0.19.0) (2026-09-09)
4
+
5
+
6
+ ### Features
7
+
8
+ * add resumable child workflows and Studio inspection ([#157](https://github.com/kortyx-io/kortyx/issues/157)) ([542ba29](https://github.com/kortyx-io/kortyx/commit/542ba29b3b57217e6e5d98c1b409bbec5ce3261f))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @kortyx/core bumped to 0.6.0
16
+ * @kortyx/telemetry-contracts bumped to 0.3.0
17
+
18
+ ## [0.18.0](https://github.com/kortyx-io/kortyx/compare/hooks-v0.17.1...hooks-v0.18.0) (2026-08-02)
19
+
20
+
21
+ ### Features
22
+
23
+ * **studio:** release self-hosted observability preview ([4e9d3fd](https://github.com/kortyx-io/kortyx/commit/4e9d3fdbc7233f9b1de94ca1afe6dbae9cc9157f))
24
+ * **studio:** release self-hosted observability preview ([#139](https://github.com/kortyx-io/kortyx/issues/139)) ([4e9d3fd](https://github.com/kortyx-io/kortyx/commit/4e9d3fdbc7233f9b1de94ca1afe6dbae9cc9157f))
25
+ * **telemetry:** add persisted studio read models ([cebb06b](https://github.com/kortyx-io/kortyx/commit/cebb06bb74ccd4422e263f0ff4efa0fee5a16240))
26
+
27
+
28
+ ### Dependencies
29
+
30
+ * The following workspace dependencies were updated
31
+ * dependencies
32
+ * @kortyx/telemetry-contracts bumped to 0.2.0
33
+
3
34
  ## [0.17.1](https://github.com/kortyx-io/kortyx/compare/hooks-v0.17.0...hooks-v0.17.1) (2026-05-28)
4
35
 
5
36
 
package/LICENSE CHANGED
@@ -1,10 +1,18 @@
1
+ Copyright 2026 Kortyx contributors
2
+
3
+ Portions of this software are licensed as follows:
4
+
5
+ - All content that resides under the `apps/studio/` directory of this repository is licensed under the license defined in `apps/studio/LICENSE.md`.
6
+ - All content that resides under a `plugins/*/` directory of this repository is licensed under the license defined in that plugin's `LICENSE.md`.
7
+ - All third party components incorporated into the Kortyx software are licensed under the original license provided by the owner of the applicable component.
8
+ - Content outside of the above mentioned directories or restrictions is available under the Apache License, Version 2.0, as defined below.
9
+
1
10
  Apache License
2
11
  Version 2.0, January 2004
3
12
  http://www.apache.org/licenses/
4
13
 
5
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
-
7
- 1. Definitions.
14
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
15
+ 1. Definitions.
8
16
 
9
17
  "License" shall mean the terms and conditions for use, reproduction,
10
18
  and distribution as defined by Sections 1 through 9 of this document.
@@ -63,14 +71,14 @@
63
71
  on behalf of whom a Contribution has been received by Licensor and
64
72
  subsequently incorporated within the Work.
65
73
 
66
- 2. Grant of Copyright License. Subject to the terms and conditions of
74
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
75
  this License, each Contributor hereby grants to You a perpetual,
68
76
  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
77
  copyright license to reproduce, prepare Derivative Works of,
70
78
  publicly display, publicly perform, sublicense, and distribute the
71
79
  Work and such Derivative Works in Source or Object form.
72
80
 
73
- 3. Grant of Patent License. Subject to the terms and conditions of
81
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
82
  this License, each Contributor hereby grants to You a perpetual,
75
83
  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
84
  (except as stated in this section) patent license to make, have made,
@@ -86,39 +94,39 @@
86
94
  granted to You under this License for that Work shall terminate
87
95
  as of the date such litigation is filed.
88
96
 
89
- 4. Redistribution. You may reproduce and distribute copies of the
97
+ 4. Redistribution. You may reproduce and distribute copies of the
90
98
  Work or Derivative Works thereof in any medium, with or without
91
99
  modifications, and in Source or Object form, provided that You
92
100
  meet the following conditions:
93
101
 
94
102
  (a) You must give any other recipients of the Work or
95
- Derivative Works a copy of this License; and
103
+ Derivative Works a copy of this License; and
96
104
 
97
105
  (b) You must cause any modified files to carry prominent notices
98
- stating that You changed the files; and
106
+ stating that You changed the files; and
99
107
 
100
108
  (c) You must retain, in the Source form of any Derivative Works
101
- that You distribute, all copyright, patent, trademark, and
102
- attribution notices from the Source form of the Work,
103
- excluding those notices that do not pertain to any part of
104
- the Derivative Works; and
109
+ that You distribute, all copyright, patent, trademark, and
110
+ attribution notices from the Source form of the Work,
111
+ excluding those notices that do not pertain to any part of
112
+ the Derivative Works; and
105
113
 
106
114
  (d) If the Work includes a "NOTICE" text file as part of its
107
- distribution, then any Derivative Works that You distribute must
108
- include a readable copy of the attribution notices contained
109
- within such NOTICE file, excluding those notices that do not
110
- pertain to any part of the Derivative Works, in at least one
111
- of the following places: within a NOTICE text file distributed
112
- as part of the Derivative Works; within the Source form or
113
- documentation, if provided along with the Derivative Works; or,
114
- within a display generated by the Derivative Works, if and
115
- wherever such third-party notices normally appear. The contents
116
- of the NOTICE file are for informational purposes only and
117
- do not modify the License. You may add Your own attribution
118
- notices within Derivative Works that You distribute, alongside
119
- or as an addendum to the NOTICE text from the Work, provided
120
- that such additional attribution notices cannot be construed
121
- as modifying the License.
115
+ distribution, then any Derivative Works that You distribute must
116
+ include a readable copy of the attribution notices contained
117
+ within such NOTICE file, excluding those notices that do not
118
+ pertain to any part of the Derivative Works, in at least one
119
+ of the following places: within a NOTICE text file distributed
120
+ as part of the Derivative Works; within the Source form or
121
+ documentation, if provided along with the Derivative Works; or,
122
+ within a display generated by the Derivative Works, if and
123
+ wherever such third-party notices normally appear. The contents
124
+ of the NOTICE file are for informational purposes only and
125
+ do not modify the License. You may add Your own attribution
126
+ notices within Derivative Works that You distribute, alongside
127
+ or as an addendum to the NOTICE text from the Work, provided
128
+ that such additional attribution notices cannot be construed
129
+ as modifying the License.
122
130
 
123
131
  You may add Your own copyright statement to Your modifications and
124
132
  may provide additional or different license terms and conditions
@@ -127,7 +135,7 @@
127
135
  reproduction, and distribution of the Work otherwise complies with
128
136
  the conditions stated in this License.
129
137
 
130
- 5. Submission of Contributions. Unless You explicitly state otherwise,
138
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
139
  any Contribution intentionally submitted for inclusion in the Work
132
140
  by You to the Licensor shall be under the terms and conditions of
133
141
  this License, without any additional terms or conditions.
@@ -135,12 +143,12 @@
135
143
  the terms of any separate license agreement you may have executed
136
144
  with Licensor regarding such Contributions.
137
145
 
138
- 6. Trademarks. This License does not grant permission to use the trade
146
+ 6. Trademarks. This License does not grant permission to use the trade
139
147
  names, trademarks, service marks, or product names of the Licensor,
140
148
  except as required for reasonable and customary use in describing the
141
149
  origin of the Work and reproducing the content of the NOTICE file.
142
150
 
143
- 7. Disclaimer of Warranty. Unless required by applicable law or
151
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
152
  agreed to in writing, Licensor provides the Work (and each
145
153
  Contributor provides its Contributions) on an "AS IS" BASIS,
146
154
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
@@ -150,7 +158,7 @@
150
158
  appropriateness of using or redistributing the Work and assume any
151
159
  risks associated with Your exercise of permissions under this License.
152
160
 
153
- 8. Limitation of Liability. In no event and under no legal theory,
161
+ 8. Limitation of Liability. In no event and under no legal theory,
154
162
  whether in tort (including negligence), contract, or otherwise,
155
163
  unless required by applicable law (such as deliberate and grossly
156
164
  negligent acts) or agreed to in writing, shall any Contributor be
@@ -162,7 +170,7 @@
162
170
  other commercial damages or losses), even if such Contributor
163
171
  has been advised of the possibility of such damages.
164
172
 
165
- 9. Accepting Warranty or Additional Liability. While redistributing
173
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
174
  the Work or Derivative Works thereof, You may choose to offer,
167
175
  and charge a fee for, acceptance of support, warranty, indemnity,
168
176
  or other liability obligations and/or rights consistent with this
@@ -173,9 +181,9 @@
173
181
  incurred by, or claims asserted against, such Contributor by reason
174
182
  of your accepting any such warranty or additional liability.
175
183
 
176
- END OF TERMS AND CONDITIONS
184
+ END OF TERMS AND CONDITIONS
177
185
 
178
- APPENDIX: How to apply the Apache License to your work.
186
+ APPENDIX: How to apply the Apache License to your work.
179
187
 
180
188
  To apply the Apache License to your work, attach the following
181
189
  boilerplate notice, with the fields enclosed by brackets "[]"
@@ -186,16 +194,16 @@
186
194
  same "printed page" as the copyright notice for easier
187
195
  identification within third-party archives.
188
196
 
189
- Copyright [yyyy] [name of copyright owner]
197
+ Copyright [yyyy] [name of copyright owner]
190
198
 
191
- Licensed under the Apache License, Version 2.0 (the "License");
192
- you may not use this file except in compliance with the License.
193
- You may obtain a copy of the License at
199
+ Licensed under the Apache License, Version 2.0 (the "License");
200
+ you may not use this file except in compliance with the License.
201
+ You may obtain a copy of the License at
194
202
 
195
203
  http://www.apache.org/licenses/LICENSE-2.0
196
204
 
197
- Unless required by applicable law or agreed to in writing, software
198
- distributed under the License is distributed on an "AS IS" BASIS,
199
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
- See the License for the specific language governing permissions and
201
- limitations under the License.
205
+ Unless required by applicable law or agreed to in writing, software
206
+ distributed under the License is distributed on an "AS IS" BASIS,
207
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
208
+ See the License for the specific language governing permissions and
209
+ limitations under the License.
package/README.md CHANGED
@@ -53,3 +53,31 @@ Workarounds:
53
53
  ## License
54
54
 
55
55
  Apache-2.0. See [LICENSE](https://github.com/kortyx-io/kortyx/blob/main/LICENSE).
56
+
57
+
58
+ ## Child workflows
59
+
60
+ Call a registered workflow from a node or custom hook and continue with its
61
+ result:
62
+
63
+ ```ts
64
+ const result = await useWorkflow({
65
+ id: "research",
66
+ workflow: researchWorkflow,
67
+ input: { topic: "AI agents" },
68
+ });
69
+ return { data: { summary: result.data.summary } };
70
+ ```
71
+
72
+ The child defines `inputSchema` and `outputSchema` on `defineWorkflow`.
73
+ Inputs and returned data are inferred from those schemas and validated at
74
+ runtime. The result is the child's accumulated data parsed by its output
75
+ schema. Use `createWorkflowHooks({ research: researchWorkflow })` for typed
76
+ string ids. Register the same definitions with the agent.
77
+
78
+ Calls need stable ids. A child interrupt pauses the chain; resuming replays
79
+ the enclosing node and reuses saved child results. Keep effects before calls
80
+ idempotent. Fork/rollback retain nested checkpoints; use Redis for persistence
81
+ across server restarts. Concurrent calls are unsupported.
82
+
83
+ See [the execution and replay contract](../../docs/design-specs/child-workflows.md).
package/dist/index.d.ts CHANGED
@@ -1,7 +1,63 @@
1
- import { NodeConfig, InterruptInput, InterruptResult, GraphState } from '@kortyx/core';
2
- import { R as ReasonTraceAdapter, K as KortyxTraceMetadata } from './tracing-DhjgeiDS.js';
3
- export { a as KortyxTelemetryConfig, b as KortyxTelemetryContentCapture, c as KortyxTelemetryPrompt, d as KortyxTraceAdapter, e as ReasonTraceAttributes, f as ReasonTraceSpan, g as ReasonTraceSpanEndArgs, h as ReasonTraceSpanStartArgs } from './tracing-DhjgeiDS.js';
1
+ import { WorkflowDefinition, TokenUsage, InterruptInput, NodeConfig, InterruptResult, GraphState } from '@kortyx/core';
2
+ import { K as KortyxTelemetryConfig, R as ReasonTraceAdapter, a as KortyxTraceMetadata, b as KortyxTelemetryEventType } from './tracing-BI6YKHSf.js';
3
+ export { E as EnsureWorkflowTopologyRequest, c as EnsureWorkflowTopologyResponse, d as KortyxTelemetryContentCapture, e as KortyxTelemetryCorrelation, f as KortyxTelemetryEvent, g as KortyxTelemetryPrompt, h as KortyxTelemetryReporter, i as KortyxTelemetryService, j as KortyxTraceAdapter, k as ReasonTraceAttributes, l as ReasonTraceSpan, m as ReasonTraceSpanEndArgs, n as ReasonTraceSpanStartArgs } from './tracing-BI6YKHSf.js';
4
+ import { z } from 'zod';
4
5
  import { ProviderModelRef, KortyxReasoningOptions, KortyxResponseFormat, KortyxExecutableTool, KortyxUsage, KortyxFinishReason, KortyxProviderMetadata, KortyxWarning, KortyxToolCall, KortyxToolResult } from '@kortyx/providers';
6
+ import '@kortyx/telemetry-contracts';
7
+
8
+ type WorkflowCallOutcome = {
9
+ status: "completed";
10
+ data: Record<string, unknown>;
11
+ usage?: TokenUsage | undefined;
12
+ } | {
13
+ status: "interrupted";
14
+ request: InterruptInput;
15
+ snapshot: unknown;
16
+ };
17
+ type WorkflowCallService = (args: {
18
+ id: string;
19
+ workflow: string;
20
+ input: unknown;
21
+ snapshot?: unknown;
22
+ definition?: WorkflowDefinition | undefined;
23
+ invocationId: string;
24
+ response?: string | string[] | undefined;
25
+ callerNodeExecutionId?: string | undefined;
26
+ }) => Promise<WorkflowCallOutcome>;
27
+ declare class WorkflowCallError extends Error {
28
+ readonly workflow: string;
29
+ constructor(workflow: string, message: string);
30
+ }
31
+ declare function workflowCallFingerprint(value: unknown): string;
32
+ type SchematizedWorkflow = WorkflowDefinition & {
33
+ inputSchema: z.ZodType;
34
+ outputSchema: z.ZodType<Record<string, unknown>>;
35
+ };
36
+ /** Bind string workflow ids to the same definitions registered with the agent. */
37
+ declare function createWorkflowHooks<const W extends Record<string, SchematizedWorkflow>>(workflows: W): {
38
+ useWorkflow: <K extends keyof W & string>(args: {
39
+ id: string;
40
+ workflow: K;
41
+ input: z.input<W[K]["inputSchema"]>;
42
+ }) => Promise<{
43
+ data: z.output<W[K]["outputSchema"]>;
44
+ }>;
45
+ };
46
+ /** Typed references avoid caller-supplied result type assertions. */
47
+ declare function useWorkflow<W extends SchematizedWorkflow>(args: {
48
+ id: string;
49
+ workflow: W;
50
+ input: z.input<W["inputSchema"]>;
51
+ }): Promise<{
52
+ data: z.output<W["outputSchema"]>;
53
+ }>;
54
+ declare function useWorkflow(args: {
55
+ id: string;
56
+ workflow: string;
57
+ input: unknown;
58
+ }): Promise<{
59
+ data: Record<string, unknown>;
60
+ }>;
5
61
 
6
62
  type HookNodeRuntimeContext = {
7
63
  graph: {
@@ -11,6 +67,8 @@ type HookNodeRuntimeContext = {
11
67
  config: NodeConfig;
12
68
  emit: (event: string, payload: unknown) => void;
13
69
  awaitInterrupt: (args: InterruptInput) => InterruptResult;
70
+ callWorkflow?: WorkflowCallService;
71
+ workflowCallTelemetry?: KortyxTelemetryConfig | undefined;
14
72
  };
15
73
  type HookRuntimeContext = {
16
74
  node: HookNodeRuntimeContext;
@@ -160,4 +218,7 @@ declare function useRuntimeContext<TContext extends Record<string, unknown> = Re
160
218
  declare function useNodeState<T>(initialValue: T): [T, StateSetter<T>];
161
219
  declare function useWorkflowState<T>(key: string, ...rest: [] | [T]): [T, StateSetter<T>];
162
220
 
163
- export { type HookRuntimeContext, KortyxTraceMetadata, ReasonTraceAdapter, type UseInterruptArgs, type UseReasonArgs, type UseReasonResult, type UseReasonStep, type UseReasonToolExecution, type UseStructuredDataArgs, runWithHookContext, useInterrupt, useNodeState, useReason, useRuntimeContext, useStructuredData, useWorkflowState };
221
+ /** Best effort lifecycle facts. Engine snapshots and interrupt tokens never leave the runtime. */
222
+ declare function emitWorkflowCall(telemetry: KortyxTelemetryConfig | undefined, type: KortyxTelemetryEventType, payload: Record<string, unknown>): void;
223
+
224
+ export { type HookRuntimeContext, KortyxTelemetryConfig, KortyxTelemetryEventType, KortyxTraceMetadata, ReasonTraceAdapter, type UseInterruptArgs, type UseReasonArgs, type UseReasonResult, type UseReasonStep, type UseReasonToolExecution, type UseStructuredDataArgs, WorkflowCallError, type WorkflowCallOutcome, type WorkflowCallService, createWorkflowHooks, emitWorkflowCall, runWithHookContext, useInterrupt, useNodeState, useReason, useRuntimeContext, useStructuredData, useWorkflow, useWorkflowState, workflowCallFingerprint };
package/dist/index.js CHANGED
@@ -19,13 +19,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  // src/index.ts
20
20
  var index_exports = {};
21
21
  __export(index_exports, {
22
+ WorkflowCallError: () => WorkflowCallError,
23
+ createWorkflowHooks: () => createWorkflowHooks,
24
+ emitWorkflowCall: () => emitWorkflowCall,
22
25
  runWithHookContext: () => runWithHookContext,
23
26
  useInterrupt: () => useInterrupt,
24
27
  useNodeState: () => useNodeState,
25
28
  useReason: () => useReason2,
26
29
  useRuntimeContext: () => useRuntimeContext,
27
30
  useStructuredData: () => useStructuredData,
28
- useWorkflowState: () => useWorkflowState
31
+ useWorkflow: () => useWorkflow,
32
+ useWorkflowState: () => useWorkflowState,
33
+ workflowCallFingerprint: () => workflowCallFingerprint
29
34
  });
30
35
  module.exports = __toCommonJS(index_exports);
31
36
 
@@ -78,6 +83,8 @@ var createInternalContext = (ctx) => {
78
83
  const currentNodeState = storedNodeId && storedNodeId === nodeId ? normalizeNodeState(storedState) : { byIndex: [], byKey: {} };
79
84
  return {
80
85
  ...ctx,
86
+ workflowCallActive: false,
87
+ workflowCallIds: /* @__PURE__ */ new Set(),
81
88
  nodeStateIndex: 0,
82
89
  reasonCallIndex: 0,
83
90
  currentNodeState,
@@ -151,6 +158,12 @@ async function runWithHookContext(ctx, fn) {
151
158
  try {
152
159
  const result = await storage.run(internal, fn);
153
160
  cleanupCompletedReasonCheckpoints(internal);
161
+ for (const key of Object.keys(internal.currentNodeState.byKey)) {
162
+ if (key.startsWith("__useWorkflow:") || key === "__useWorkflowActivation") {
163
+ internal.currentNodeState.byKey[key] = null;
164
+ internal.stateDirty = true;
165
+ }
166
+ }
154
167
  return { result, runtimeUpdates: buildRuntimeStateUpdates(internal) };
155
168
  } catch (err) {
156
169
  const runtimeUpdates = buildRuntimeStateUpdates(internal);
@@ -248,7 +261,10 @@ var awaitInterruptInternal = (args) => {
248
261
  ...typeof args.id === "string" && args.id.length > 0 ? { id: args.id } : {},
249
262
  meta: {
250
263
  ...userMeta,
251
- __kortyxResumeStatePatch: resumeStatePatch
264
+ __kortyxResumeStatePatch: {
265
+ ...resumeStatePatch,
266
+ ...ctx.tokenUsage ? { tokenUsage: ctx.tokenUsage } : {}
267
+ }
252
268
  }
253
269
  };
254
270
  const raw = ctx.node.awaitInterrupt(enrichedRequest);
@@ -424,6 +440,17 @@ async function runReasonEngine(args) {
424
440
  input: args.telemetry?.input ?? messages
425
441
  }
426
442
  });
443
+ const modelRequestStartedAt = Date.now();
444
+ let firstTextTokenAt;
445
+ let lastTextTokenAt;
446
+ const generationTimingAttributes = () => firstTextTokenAt === void 0 || lastTextTokenAt === void 0 ? {} : {
447
+ ttftMs: Math.max(0, firstTextTokenAt - modelRequestStartedAt),
448
+ streamDurationMs: Math.max(0, lastTextTokenAt - firstTextTokenAt),
449
+ timeToLastTokenMs: Math.max(
450
+ 0,
451
+ lastTextTokenAt - modelRequestStartedAt
452
+ )
453
+ };
427
454
  const endTrace = (span, result) => {
428
455
  span?.end?.({
429
456
  ...result.usage !== void 0 ? { usage: result.usage } : {},
@@ -431,7 +458,8 @@ async function runReasonEngine(args) {
431
458
  ...result.providerMetadata !== void 0 ? { providerMetadata: result.providerMetadata } : {},
432
459
  ...result.warnings !== void 0 ? { warnings: result.warnings } : {},
433
460
  attributes: {
434
- textLength: result.text.length
461
+ textLength: result.text.length,
462
+ ...generationTimingAttributes()
435
463
  },
436
464
  telemetry: {
437
465
  ...args.telemetry ?? {},
@@ -466,6 +494,9 @@ async function runReasonEngine(args) {
466
494
  switch (chunk.type) {
467
495
  case "text-delta": {
468
496
  if (chunk.delta.length === 0) break;
497
+ const tokenAt = Date.now();
498
+ firstTextTokenAt ??= tokenAt;
499
+ lastTextTokenAt = tokenAt;
469
500
  final += chunk.delta;
470
501
  args.onTextChunk?.(chunk.delta);
471
502
  if (chunk.raw !== void 0) {
@@ -1709,6 +1740,11 @@ var runReasonToolLoop = async (args) => {
1709
1740
  tool.name,
1710
1741
  false
1711
1742
  );
1743
+ traceSpan?.addEvent?.("useReason.tool-call.start", {
1744
+ stepIndex,
1745
+ tool: tool.name,
1746
+ toolCallId: toolCall.id
1747
+ });
1712
1748
  if (shouldEmitTool) {
1713
1749
  emitToolEvent("tool-call-start", {
1714
1750
  tool: tool.name,
@@ -2311,14 +2347,243 @@ function useWorkflowState(key, ...rest) {
2311
2347
  };
2312
2348
  return [getValue(), setValue];
2313
2349
  }
2350
+
2351
+ // src/workflow.ts
2352
+ var import_node_crypto5 = require("crypto");
2353
+
2354
+ // src/workflow-telemetry.ts
2355
+ var import_node_crypto4 = require("crypto");
2356
+ function emitWorkflowCall(telemetry, type, payload) {
2357
+ const base = telemetry?.correlation;
2358
+ if (!telemetry?.reporter || !telemetry.environment || !telemetry.service || !base?.runId || !base.workflowId)
2359
+ return;
2360
+ try {
2361
+ const active = telemetry.trace?.getActiveContext?.();
2362
+ void Promise.resolve(
2363
+ telemetry.reporter.emit([
2364
+ {
2365
+ schemaVersion: 1,
2366
+ eventId: (0, import_node_crypto4.randomUUID)(),
2367
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
2368
+ environment: telemetry.environment,
2369
+ service: telemetry.service,
2370
+ correlation: {
2371
+ ...base,
2372
+ runId: base.runId,
2373
+ workflowId: base.workflowId,
2374
+ ...active
2375
+ },
2376
+ type,
2377
+ payload
2378
+ }
2379
+ ])
2380
+ ).catch(() => {
2381
+ });
2382
+ } catch {
2383
+ }
2384
+ }
2385
+ function workflowCallContent(telemetry, side, value) {
2386
+ const policy = telemetry?.captureContent;
2387
+ if (!(policy === true || policy && typeof policy === "object" && policy[side]))
2388
+ return {};
2389
+ const serialized = JSON.stringify(value);
2390
+ return serialized && Buffer.byteLength(serialized, "utf8") <= 16384 ? { [side]: JSON.parse(serialized) } : { [`${side}Omitted`]: "Content exceeds 16 KiB" };
2391
+ }
2392
+
2393
+ // src/workflow.ts
2394
+ var WorkflowCallError = class extends Error {
2395
+ constructor(workflow, message) {
2396
+ super(`Workflow '${workflow}': ${message}`);
2397
+ this.workflow = workflow;
2398
+ this.name = "WorkflowCallError";
2399
+ }
2400
+ workflow;
2401
+ };
2402
+ function workflowCallFingerprint(value) {
2403
+ const seen = /* @__PURE__ */ new Set();
2404
+ const canonical = (v) => {
2405
+ if (v === null || typeof v === "string" || typeof v === "boolean") return v;
2406
+ if (typeof v === "number" && Number.isFinite(v)) return v;
2407
+ if (typeof v !== "object" || !v || seen.has(v)) {
2408
+ throw new Error("Workflow input and output must be JSON serializable.");
2409
+ }
2410
+ if (!Array.isArray(v) && Object.getPrototypeOf(v) !== Object.prototype) {
2411
+ throw new Error(
2412
+ "Workflow input and output must contain plain JSON objects."
2413
+ );
2414
+ }
2415
+ seen.add(v);
2416
+ const result = Array.isArray(v) ? v.map(canonical) : Object.fromEntries(
2417
+ Object.keys(v).sort().map((key) => [
2418
+ key,
2419
+ canonical(v[key])
2420
+ ])
2421
+ );
2422
+ seen.delete(v);
2423
+ return result;
2424
+ };
2425
+ return JSON.stringify(canonical(value));
2426
+ }
2427
+ function createWorkflowHooks(workflows) {
2428
+ return {
2429
+ useWorkflow: (args) => {
2430
+ const workflow = workflows[args.workflow];
2431
+ if (!workflow || workflow.id !== args.workflow)
2432
+ throw new Error("Workflow registry keys must match definition ids.");
2433
+ return useWorkflow({ ...args, workflow });
2434
+ }
2435
+ };
2436
+ }
2437
+ async function useWorkflow(args) {
2438
+ const ctx = getHookContext();
2439
+ const service = ctx.node.callWorkflow;
2440
+ if (!service)
2441
+ throw new Error("useWorkflow requires an agent workflow registry.");
2442
+ if (!args.id.trim())
2443
+ throw new Error("useWorkflow requires a stable, nonempty id.");
2444
+ if (ctx.workflowCallActive)
2445
+ throw new Error(
2446
+ "Concurrent workflow calls are not supported; await each call."
2447
+ );
2448
+ if (ctx.workflowCallIds.has(args.id))
2449
+ throw new Error(
2450
+ `Duplicate workflow call id '${args.id}' in one node activation.`
2451
+ );
2452
+ if (ctx.workflowCallIds.size >= 64)
2453
+ throw new Error(
2454
+ "A node may call at most 64 child workflows per activation."
2455
+ );
2456
+ ctx.workflowCallIds.add(args.id);
2457
+ const definition = typeof args.workflow === "string" ? void 0 : args.workflow;
2458
+ const workflow = definition?.id ?? args.workflow;
2459
+ const input = args.input;
2460
+ const fingerprint = workflowCallFingerprint({
2461
+ workflow,
2462
+ input,
2463
+ version: definition?.version ?? null
2464
+ });
2465
+ const key = `__useWorkflow:${args.id}`;
2466
+ let record = ctx.currentNodeState.byKey[key];
2467
+ if (record && record.fingerprint !== fingerprint)
2468
+ throw new WorkflowCallError(
2469
+ workflow,
2470
+ "call input or target changed during replay"
2471
+ );
2472
+ record ??= {
2473
+ fingerprint,
2474
+ invocationId: (0, import_node_crypto5.randomUUID)(),
2475
+ status: "running",
2476
+ interrupts: []
2477
+ };
2478
+ ctx.currentNodeState.byKey[key] = record;
2479
+ ctx.stateDirty = true;
2480
+ ctx.workflowCallActive = true;
2481
+ const telemetry = ctx.node.workflowCallTelemetry;
2482
+ const activationKey = "__useWorkflowActivation";
2483
+ ctx.currentNodeState.byKey[activationKey] ??= (0, import_node_crypto5.randomUUID)();
2484
+ const callerNodeExecutionId = ctx.currentNodeState.byKey[activationKey];
2485
+ const metadata = {
2486
+ invocationId: record.invocationId,
2487
+ parentInvocationId: telemetry?.correlation?.invocationId ?? null,
2488
+ branchId: telemetry?.correlation?.branchId ?? telemetry?.correlation?.runId ?? "unknown",
2489
+ callId: args.id,
2490
+ callerNodeId: ctx.node.graph.node,
2491
+ callerNodeExecutionId,
2492
+ sourceWorkflowId: ctx.node.graph.name,
2493
+ targetWorkflowId: workflow,
2494
+ targetVersion: definition?.version ?? null
2495
+ };
2496
+ record.telemetry = metadata;
2497
+ const report = (status, content = {}) => {
2498
+ record.sequence = (record.sequence ?? 0) + 1;
2499
+ emitWorkflowCall(telemetry, `workflow.call.${status}`, {
2500
+ ...metadata,
2501
+ sequence: record.sequence,
2502
+ ...content
2503
+ });
2504
+ };
2505
+ const cached = record.status === "completed";
2506
+ try {
2507
+ for (const interruption of record.interrupts) {
2508
+ interruption.response = await awaitInterruptInternal({
2509
+ request: interruption.request,
2510
+ ...interruption.request.meta ? { meta: interruption.request.meta } : {}
2511
+ });
2512
+ }
2513
+ if (record.status === "failed")
2514
+ throw new WorkflowCallError(workflow, record.error ?? "child failed");
2515
+ while (record.status !== "completed") {
2516
+ let outcome;
2517
+ try {
2518
+ report(
2519
+ record.snapshot ? "resumed" : "started",
2520
+ workflowCallContent(telemetry, "input", input)
2521
+ );
2522
+ outcome = await service({
2523
+ callerNodeExecutionId,
2524
+ id: args.id,
2525
+ workflow,
2526
+ input,
2527
+ definition,
2528
+ invocationId: record.invocationId,
2529
+ ...record.snapshot ? { snapshot: record.snapshot } : {},
2530
+ ...record.interrupts.length ? { response: record.interrupts.at(-1)?.response } : {}
2531
+ });
2532
+ } catch (error) {
2533
+ record.status = "failed";
2534
+ record.error = error instanceof Error ? error.message : String(error);
2535
+ report("failed", { error: "Child workflow failed" });
2536
+ throw new WorkflowCallError(workflow, record.error);
2537
+ }
2538
+ if (outcome.status === "completed") {
2539
+ record.data = outcome.data;
2540
+ accumulateTokenUsage(outcome.usage);
2541
+ workflowCallFingerprint(record.data);
2542
+ record.status = "completed";
2543
+ delete record.snapshot;
2544
+ report(
2545
+ "completed",
2546
+ workflowCallContent(telemetry, "output", record.data)
2547
+ );
2548
+ } else {
2549
+ record.status = "interrupted";
2550
+ record.snapshot = outcome.snapshot;
2551
+ report("suspended", {
2552
+ leaf: outcome.request.meta?.workflowCall ?? null
2553
+ });
2554
+ const interruption = {
2555
+ request: outcome.request
2556
+ };
2557
+ record.interrupts.push(interruption);
2558
+ interruption.response = await awaitInterruptInternal({
2559
+ request: interruption.request,
2560
+ ...interruption.request.meta ? { meta: interruption.request.meta } : {}
2561
+ });
2562
+ }
2563
+ }
2564
+ if (cached)
2565
+ report("reused", workflowCallContent(telemetry, "output", record.data));
2566
+ const data = record.data ?? {};
2567
+ return {
2568
+ data: JSON.parse(JSON.stringify(data))
2569
+ };
2570
+ } finally {
2571
+ ctx.workflowCallActive = false;
2572
+ }
2573
+ }
2314
2574
  // Annotate the CommonJS export names for ESM import in node:
2315
2575
  0 && (module.exports = {
2576
+ WorkflowCallError,
2577
+ createWorkflowHooks,
2578
+ emitWorkflowCall,
2316
2579
  runWithHookContext,
2317
2580
  useInterrupt,
2318
2581
  useNodeState,
2319
2582
  useReason,
2320
2583
  useRuntimeContext,
2321
2584
  useStructuredData,
2322
- useWorkflowState
2585
+ useWorkflow,
2586
+ useWorkflowState,
2587
+ workflowCallFingerprint
2323
2588
  });
2324
2589
  //# sourceMappingURL=index.js.map