@contractspec/integration.workflow-devkit 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.
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # @contractspec/integration.workflow-devkit
2
+
3
+ Workflow DevKit compiler and runtime bridges for ContractSpec `WorkflowSpec`.
4
+
5
+ ## What it provides
6
+
7
+ - Compile a `WorkflowSpec` into a deterministic Workflow DevKit manifest.
8
+ - Generate Workflow, Next.js, and generic-host scaffolding.
9
+ - Run a `WorkflowSpec` through injected Workflow DevKit primitives such as `sleep`, hooks, and webhooks.
10
+ - Create a Workflow-backed `AgentRuntimeAdapterBundle` for `@contractspec/lib.ai-agent`.
11
+ - Expose chat route helpers and re-export `WorkflowChatTransport` for reconnectable chat UIs.
12
+
13
+ ## Entry points
14
+
15
+ - `@contractspec/integration.workflow-devkit`
16
+ - `@contractspec/integration.workflow-devkit/compiler`
17
+ - `@contractspec/integration.workflow-devkit/runtime`
18
+ - `@contractspec/integration.workflow-devkit/chat-routes`
19
+ - `@contractspec/integration.workflow-devkit/agent-adapter`
20
+ - `@contractspec/integration.workflow-devkit/next`
21
+
22
+ ## Next.js example
23
+
24
+ ```ts
25
+ import { withContractSpecWorkflow } from "@contractspec/integration.workflow-devkit/next";
26
+ import { createWorkflowChatRoutes } from "@contractspec/module.ai-chat/core/workflow";
27
+
28
+ export default withContractSpecWorkflow({
29
+ experimental: {},
30
+ });
31
+
32
+ const routes = createWorkflowChatRoutes({
33
+ workflow: async (payload) => payload,
34
+ getFollowUpToken({ runId }) {
35
+ return `chat:${runId}`;
36
+ },
37
+ });
38
+
39
+ export const POST = routes.start;
40
+ ```
41
+
42
+ ## Generic host example
43
+
44
+ ```ts
45
+ import { createHook, createWebhook, sleep } from "workflow";
46
+ import {
47
+ runWorkflowSpecWithWorkflowDevkit,
48
+ type WorkflowDevkitRuntimeBridge,
49
+ } from "@contractspec/integration.workflow-devkit";
50
+ import { onboardingWorkflow } from "./onboarding.workflow";
51
+
52
+ export async function runOnboarding(input: Record<string, unknown>, bridge: WorkflowDevkitRuntimeBridge) {
53
+ "use workflow";
54
+
55
+ return runWorkflowSpecWithWorkflowDevkit({
56
+ spec: onboardingWorkflow,
57
+ initialData: input,
58
+ bridge,
59
+ primitives: {
60
+ sleep,
61
+ createHook,
62
+ createWebhook,
63
+ },
64
+ });
65
+ }
66
+ ```
@@ -0,0 +1,35 @@
1
+ import type { AgentCheckpointEnvelope, AgentRuntimeAdapterBundle } from '@contractspec/lib.ai-agent/interop';
2
+ import type { AgentSessionState } from '@contractspec/lib.ai-agent/types';
3
+ import type { WorkflowApiLike } from './types';
4
+ export interface WorkflowDevkitSuspensionState {
5
+ input?: string;
6
+ metadata?: Record<string, string>;
7
+ reason: string;
8
+ resumedAt?: Date;
9
+ suspendedAt: Date;
10
+ }
11
+ export interface WorkflowDevkitSuspensionStore {
12
+ clear(sessionId: string): Promise<void>;
13
+ get(sessionId: string): Promise<WorkflowDevkitSuspensionState | null>;
14
+ set(sessionId: string, state: WorkflowDevkitSuspensionState): Promise<void>;
15
+ }
16
+ export interface CreateWorkflowDevkitAgentRuntimeAdapterOptions {
17
+ checkpointStore?: InMemoryWorkflowDevkitCheckpointStore;
18
+ resolveSessionToken?: (sessionId: string) => string;
19
+ suspensionStore?: WorkflowDevkitSuspensionStore;
20
+ workflowApi?: Pick<WorkflowApiLike, 'resumeHook'>;
21
+ }
22
+ export declare class InMemoryWorkflowDevkitCheckpointStore {
23
+ private readonly envelopes;
24
+ delete(sessionId: string): Promise<void>;
25
+ load(sessionId: string): Promise<AgentCheckpointEnvelope | null>;
26
+ save(envelope: AgentCheckpointEnvelope): Promise<void>;
27
+ }
28
+ export declare class InMemoryWorkflowDevkitSuspensionStore implements WorkflowDevkitSuspensionStore {
29
+ private readonly states;
30
+ clear(sessionId: string): Promise<void>;
31
+ get(sessionId: string): Promise<WorkflowDevkitSuspensionState | null>;
32
+ set(sessionId: string, state: WorkflowDevkitSuspensionState): Promise<void>;
33
+ }
34
+ export declare function createWorkflowDevkitAgentRuntimeAdapterBundle(options?: CreateWorkflowDevkitAgentRuntimeAdapterOptions): AgentRuntimeAdapterBundle<AgentSessionState>;
35
+ export declare function defaultWorkflowDevkitAgentToken(sessionId: string): string;
@@ -0,0 +1,80 @@
1
+ // @bun
2
+ // src/agent-adapter.ts
3
+ class InMemoryWorkflowDevkitCheckpointStore {
4
+ envelopes = new Map;
5
+ async delete(sessionId) {
6
+ this.envelopes.delete(sessionId);
7
+ }
8
+ async load(sessionId) {
9
+ return this.envelopes.get(sessionId) ?? null;
10
+ }
11
+ async save(envelope) {
12
+ this.envelopes.set(envelope.sessionId, envelope);
13
+ }
14
+ }
15
+
16
+ class InMemoryWorkflowDevkitSuspensionStore {
17
+ states = new Map;
18
+ async clear(sessionId) {
19
+ this.states.delete(sessionId);
20
+ }
21
+ async get(sessionId) {
22
+ return this.states.get(sessionId) ?? null;
23
+ }
24
+ async set(sessionId, state) {
25
+ this.states.set(sessionId, state);
26
+ }
27
+ }
28
+ function createWorkflowDevkitAgentRuntimeAdapterBundle(options = {}) {
29
+ const checkpointStore = options.checkpointStore ?? new InMemoryWorkflowDevkitCheckpointStore;
30
+ const suspensionStore = options.suspensionStore ?? new InMemoryWorkflowDevkitSuspensionStore;
31
+ const resolveSessionToken = options.resolveSessionToken ?? defaultWorkflowDevkitAgentToken;
32
+ return {
33
+ key: "workflow-devkit",
34
+ checkpoint: {
35
+ delete(sessionId) {
36
+ return checkpointStore.delete(sessionId);
37
+ },
38
+ load(sessionId) {
39
+ return checkpointStore.load(sessionId);
40
+ },
41
+ save(envelope) {
42
+ return checkpointStore.save(envelope);
43
+ }
44
+ },
45
+ suspendResume: {
46
+ async resume(params) {
47
+ await suspensionStore.set(params.sessionId, {
48
+ input: params.input,
49
+ metadata: params.metadata,
50
+ reason: "resumed",
51
+ resumedAt: new Date,
52
+ suspendedAt: new Date
53
+ });
54
+ if (options.workflowApi) {
55
+ await options.workflowApi.resumeHook(resolveSessionToken(params.sessionId), {
56
+ input: params.input,
57
+ metadata: params.metadata,
58
+ sessionId: params.sessionId
59
+ });
60
+ }
61
+ },
62
+ async suspend(params) {
63
+ await suspensionStore.set(params.sessionId, {
64
+ metadata: params.metadata,
65
+ reason: params.reason,
66
+ suspendedAt: new Date
67
+ });
68
+ }
69
+ }
70
+ };
71
+ }
72
+ function defaultWorkflowDevkitAgentToken(sessionId) {
73
+ return `agent-session:${sessionId}`;
74
+ }
75
+ export {
76
+ defaultWorkflowDevkitAgentToken,
77
+ createWorkflowDevkitAgentRuntimeAdapterBundle,
78
+ InMemoryWorkflowDevkitSuspensionStore,
79
+ InMemoryWorkflowDevkitCheckpointStore
80
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,79 @@
1
+ // src/agent-adapter.ts
2
+ class InMemoryWorkflowDevkitCheckpointStore {
3
+ envelopes = new Map;
4
+ async delete(sessionId) {
5
+ this.envelopes.delete(sessionId);
6
+ }
7
+ async load(sessionId) {
8
+ return this.envelopes.get(sessionId) ?? null;
9
+ }
10
+ async save(envelope) {
11
+ this.envelopes.set(envelope.sessionId, envelope);
12
+ }
13
+ }
14
+
15
+ class InMemoryWorkflowDevkitSuspensionStore {
16
+ states = new Map;
17
+ async clear(sessionId) {
18
+ this.states.delete(sessionId);
19
+ }
20
+ async get(sessionId) {
21
+ return this.states.get(sessionId) ?? null;
22
+ }
23
+ async set(sessionId, state) {
24
+ this.states.set(sessionId, state);
25
+ }
26
+ }
27
+ function createWorkflowDevkitAgentRuntimeAdapterBundle(options = {}) {
28
+ const checkpointStore = options.checkpointStore ?? new InMemoryWorkflowDevkitCheckpointStore;
29
+ const suspensionStore = options.suspensionStore ?? new InMemoryWorkflowDevkitSuspensionStore;
30
+ const resolveSessionToken = options.resolveSessionToken ?? defaultWorkflowDevkitAgentToken;
31
+ return {
32
+ key: "workflow-devkit",
33
+ checkpoint: {
34
+ delete(sessionId) {
35
+ return checkpointStore.delete(sessionId);
36
+ },
37
+ load(sessionId) {
38
+ return checkpointStore.load(sessionId);
39
+ },
40
+ save(envelope) {
41
+ return checkpointStore.save(envelope);
42
+ }
43
+ },
44
+ suspendResume: {
45
+ async resume(params) {
46
+ await suspensionStore.set(params.sessionId, {
47
+ input: params.input,
48
+ metadata: params.metadata,
49
+ reason: "resumed",
50
+ resumedAt: new Date,
51
+ suspendedAt: new Date
52
+ });
53
+ if (options.workflowApi) {
54
+ await options.workflowApi.resumeHook(resolveSessionToken(params.sessionId), {
55
+ input: params.input,
56
+ metadata: params.metadata,
57
+ sessionId: params.sessionId
58
+ });
59
+ }
60
+ },
61
+ async suspend(params) {
62
+ await suspensionStore.set(params.sessionId, {
63
+ metadata: params.metadata,
64
+ reason: params.reason,
65
+ suspendedAt: new Date
66
+ });
67
+ }
68
+ }
69
+ };
70
+ }
71
+ function defaultWorkflowDevkitAgentToken(sessionId) {
72
+ return `agent-session:${sessionId}`;
73
+ }
74
+ export {
75
+ defaultWorkflowDevkitAgentToken,
76
+ createWorkflowDevkitAgentRuntimeAdapterBundle,
77
+ InMemoryWorkflowDevkitSuspensionStore,
78
+ InMemoryWorkflowDevkitCheckpointStore
79
+ };
@@ -0,0 +1,100 @@
1
+ // src/chat-routes.ts
2
+ import { createUIMessageStreamResponse } from "ai";
3
+ import { getRun, resumeHook, start } from "workflow/api";
4
+ var WORKFLOW_RUN_ID_HEADER = "x-workflow-run-id";
5
+ var WORKFLOW_STREAM_TAIL_INDEX_HEADER = "x-workflow-stream-tail-index";
6
+ function createWorkflowDevkitStartRoute(options) {
7
+ const workflowApi = options.workflowApi ?? { start };
8
+ return async (request) => {
9
+ const body = await request.json();
10
+ const args = await options.buildArgs(body, request);
11
+ const run = await workflowApi.start(options.workflow, args);
12
+ if (options.createResponse) {
13
+ return options.createResponse({ body, run });
14
+ }
15
+ if (!run.readable) {
16
+ return new Response(JSON.stringify({ runId: run.runId }), {
17
+ headers: {
18
+ "Content-Type": "application/json",
19
+ [WORKFLOW_RUN_ID_HEADER]: run.runId
20
+ }
21
+ });
22
+ }
23
+ return createUIMessageStreamResponse({
24
+ headers: {
25
+ [WORKFLOW_RUN_ID_HEADER]: run.runId
26
+ },
27
+ stream: run.readable
28
+ });
29
+ };
30
+ }
31
+ function createWorkflowDevkitFollowUpRoute(options) {
32
+ const workflowApi = options.workflowApi ?? { resumeHook };
33
+ return async (request, context) => {
34
+ const runId = context.params.runId ?? context.params.id;
35
+ if (!runId) {
36
+ return new Response(JSON.stringify({ error: "Missing run id" }), {
37
+ status: 400
38
+ });
39
+ }
40
+ const body = await request.json();
41
+ const token = options.resolveToken({ body, request, runId });
42
+ const payload = options.buildPayload ? await options.buildPayload(body, request) : body;
43
+ await workflowApi.resumeHook(token, payload);
44
+ return new Response(JSON.stringify({ ok: true }), {
45
+ headers: {
46
+ "Content-Type": "application/json",
47
+ [WORKFLOW_RUN_ID_HEADER]: runId
48
+ }
49
+ });
50
+ };
51
+ }
52
+ function createWorkflowDevkitStreamRoute(options = {}) {
53
+ const workflowApi = options.workflowApi ?? { getRun };
54
+ return async (request, context) => {
55
+ const runId = context.params.runId ?? context.params.id;
56
+ if (!runId) {
57
+ return new Response(JSON.stringify({ error: "Missing run id" }), {
58
+ status: 400
59
+ });
60
+ }
61
+ const run = workflowApi.getRun(runId);
62
+ if (!run) {
63
+ return new Response(JSON.stringify({ error: "Workflow run not found" }), {
64
+ status: 404
65
+ });
66
+ }
67
+ const startIndex = readStartIndex(request.url);
68
+ const readable = run.getReadable ? run.getReadable({ startIndex }) : run.readable;
69
+ if (!readable) {
70
+ return new Response(JSON.stringify({ error: "Run has no readable stream" }), {
71
+ status: 404
72
+ });
73
+ }
74
+ const tailIndex = await readable.getTailIndex?.();
75
+ return createUIMessageStreamResponse({
76
+ headers: {
77
+ ...tailIndex !== undefined ? {
78
+ [WORKFLOW_STREAM_TAIL_INDEX_HEADER]: String(tailIndex)
79
+ } : {}
80
+ },
81
+ stream: readable
82
+ });
83
+ };
84
+ }
85
+ function readStartIndex(url) {
86
+ const parsed = new URL(url);
87
+ const rawStartIndex = parsed.searchParams.get("startIndex");
88
+ if (!rawStartIndex) {
89
+ return;
90
+ }
91
+ const parsedStartIndex = Number(rawStartIndex);
92
+ return Number.isFinite(parsedStartIndex) ? parsedStartIndex : undefined;
93
+ }
94
+ export {
95
+ createWorkflowDevkitStreamRoute,
96
+ createWorkflowDevkitStartRoute,
97
+ createWorkflowDevkitFollowUpRoute,
98
+ WORKFLOW_STREAM_TAIL_INDEX_HEADER,
99
+ WORKFLOW_RUN_ID_HEADER
100
+ };
@@ -0,0 +1,177 @@
1
+ // src/helpers.ts
2
+ import { evaluateExpression } from "@contractspec/lib.contracts-spec/workflow";
3
+ function inferWorkflowDevkitBehavior(step) {
4
+ return step.runtime?.workflowDevkit?.behavior ?? step.type;
5
+ }
6
+ function resolveWorkflowDevkitEntryStepId(spec) {
7
+ const entryStepId = spec.definition.entryStepId ?? spec.definition.steps[0]?.id;
8
+ if (!entryStepId) {
9
+ throw new Error(`Workflow ${spec.meta.key}.v${spec.meta.version} does not define an entry step.`);
10
+ }
11
+ return entryStepId;
12
+ }
13
+ function resolveWorkflowDevkitRunIdentity(spec, runIdentity) {
14
+ if (runIdentity) {
15
+ return runIdentity;
16
+ }
17
+ const strategy = spec.runtime?.workflowDevkit?.runIdentity?.strategy ?? "meta-key-version";
18
+ const prefix = spec.runtime?.workflowDevkit?.runIdentity?.prefix;
19
+ const baseIdentity = strategy === "meta-key-version" ? `${spec.meta.key}.v${spec.meta.version}` : `${spec.meta.key}.v${spec.meta.version}`;
20
+ return prefix ? `${prefix}:${baseIdentity}` : baseIdentity;
21
+ }
22
+ function resolveWorkflowDevkitWaitToken(spec, step, runIdentity) {
23
+ const runtime = step.runtime?.workflowDevkit;
24
+ if (!runtime) {
25
+ return;
26
+ }
27
+ const explicitToken = runtime.hookWait?.token ?? runtime.webhookWait?.token ?? runtime.approvalWait?.token ?? runtime.streamSession?.followUpToken;
28
+ if (explicitToken) {
29
+ return explicitToken;
30
+ }
31
+ const tokenStrategy = spec.runtime?.workflowDevkit?.hookTokens?.strategy ?? "deterministic";
32
+ const prefix = spec.runtime?.workflowDevkit?.hookTokens?.prefix ?? spec.meta.key;
33
+ const stableStepId = sanitizeIdentifier(step.id);
34
+ if (tokenStrategy === "session-scoped") {
35
+ const resolvedRunIdentity = sanitizeIdentifier(resolveWorkflowDevkitRunIdentity(spec, runIdentity));
36
+ return `${prefix}:${resolvedRunIdentity}:${stableStepId}`;
37
+ }
38
+ if (tokenStrategy === "step-scoped") {
39
+ return `${prefix}:v${spec.meta.version}:${stableStepId}`;
40
+ }
41
+ return `${prefix}:${stableStepId}`;
42
+ }
43
+ function resolveWorkflowDevkitNextStepId(spec, step, data, input, output) {
44
+ const transitions = spec.definition.transitions.filter((transition) => transition.from === step.id);
45
+ for (const transition of transitions) {
46
+ if (evaluateExpression(transition.condition, {
47
+ data,
48
+ input,
49
+ output
50
+ })) {
51
+ return transition.to;
52
+ }
53
+ }
54
+ return null;
55
+ }
56
+ function mergeWorkflowDevkitData(current, input, output) {
57
+ const next = { ...current };
58
+ if (isRecord(input)) {
59
+ Object.assign(next, input);
60
+ }
61
+ if (isRecord(output)) {
62
+ Object.assign(next, output);
63
+ }
64
+ return next;
65
+ }
66
+ function sanitizeIdentifier(value) {
67
+ return value.replace(/[^a-zA-Z0-9_-]+/g, "-");
68
+ }
69
+ function isRecord(value) {
70
+ return value != null && typeof value === "object" && !Array.isArray(value);
71
+ }
72
+
73
+ // src/compiler.ts
74
+ function compileWorkflowSpecToWorkflowDevkit(spec) {
75
+ return {
76
+ entryStepId: resolveWorkflowDevkitEntryStepId(spec),
77
+ hostTarget: spec.runtime?.workflowDevkit?.hostTarget ?? "generic",
78
+ hookTokenStrategy: spec.runtime?.workflowDevkit?.hookTokens?.strategy ?? "deterministic",
79
+ integrationMode: spec.runtime?.workflowDevkit?.integrationMode ?? "manual",
80
+ runIdentityStrategy: spec.runtime?.workflowDevkit?.runIdentity?.strategy ?? "meta-key-version",
81
+ specKey: spec.meta.key,
82
+ specVersion: spec.meta.version,
83
+ steps: spec.definition.steps.map((step) => ({
84
+ behavior: inferWorkflowDevkitBehavior(step),
85
+ id: step.id,
86
+ label: step.label,
87
+ operationRef: step.action?.operation ? `${step.action.operation.key}.v${step.action.operation.version}` : undefined,
88
+ runtime: step.runtime?.workflowDevkit,
89
+ transitions: spec.definition.transitions.filter((transition) => transition.from === step.id).map((transition) => ({
90
+ condition: transition.condition,
91
+ to: transition.to
92
+ })),
93
+ type: step.type,
94
+ waitToken: resolveWorkflowDevkitWaitToken(spec, step)
95
+ }))
96
+ };
97
+ }
98
+ function generateWorkflowDevkitArtifacts(spec, options) {
99
+ const compilation = compileWorkflowSpecToWorkflowDevkit(spec);
100
+ const workflowFunctionName = options.workflowFunctionName ?? `${sanitizeIdentifier(options.exportName)}WorkflowDevkit`;
101
+ return {
102
+ genericBootstrap: createGenericBootstrapTemplate(options.exportName, workflowFunctionName),
103
+ manifest: JSON.stringify(compilation, null, 2),
104
+ nextFollowUpRoute: createFollowUpRouteTemplate(workflowFunctionName),
105
+ nextStartRoute: createStartRouteTemplate(workflowFunctionName),
106
+ nextStreamRoute: createStreamRouteTemplate(),
107
+ workflowModule: createWorkflowModuleTemplate(options.exportName, options.specImportPath, workflowFunctionName)
108
+ };
109
+ }
110
+ function createGenericBootstrapTemplate(exportName, workflowFunctionName) {
111
+ return `import { ${workflowFunctionName} } from "./${sanitizeIdentifier(exportName)}.workflow-devkit";
112
+
113
+ export const ${sanitizeIdentifier(exportName)}WorkflowDevkitBootstrap = {
114
+ workflow: ${workflowFunctionName},
115
+ createBridge() {
116
+ return {
117
+ executeAutomationStep: async () => {
118
+ throw new Error("Provide executeAutomationStep in your generic host bridge.");
119
+ },
120
+ };
121
+ },
122
+ };
123
+ `;
124
+ }
125
+ function createStartRouteTemplate(workflowFunctionName) {
126
+ return `import { createWorkflowDevkitStartRoute } from "@contractspec/integration.workflow-devkit";
127
+ import { ${workflowFunctionName} } from "./workflow";
128
+
129
+ export const POST = createWorkflowDevkitStartRoute({
130
+ workflow: ${workflowFunctionName},
131
+ async buildArgs(body) {
132
+ return [body];
133
+ },
134
+ });
135
+ `;
136
+ }
137
+ function createFollowUpRouteTemplate(workflowFunctionName) {
138
+ return `import { createWorkflowDevkitFollowUpRoute } from "@contractspec/integration.workflow-devkit";
139
+
140
+ export const POST = createWorkflowDevkitFollowUpRoute({
141
+ resolveToken({ runId }) {
142
+ return \`${workflowFunctionName}:\${runId}\`;
143
+ },
144
+ });
145
+ `;
146
+ }
147
+ function createStreamRouteTemplate() {
148
+ return `import { createWorkflowDevkitStreamRoute } from "@contractspec/integration.workflow-devkit";
149
+
150
+ export const GET = createWorkflowDevkitStreamRoute();
151
+ `;
152
+ }
153
+ function createWorkflowModuleTemplate(exportName, specImportPath, workflowFunctionName) {
154
+ return `import { createHook, createWebhook, sleep } from "workflow";
155
+ import { runWorkflowSpecWithWorkflowDevkit } from "@contractspec/integration.workflow-devkit";
156
+ import { ${exportName} } from "${specImportPath}";
157
+
158
+ export async function ${workflowFunctionName}(input = {}, bridge = {}) {
159
+ "use workflow";
160
+
161
+ return runWorkflowSpecWithWorkflowDevkit({
162
+ spec: ${exportName},
163
+ initialData: input,
164
+ bridge,
165
+ primitives: {
166
+ sleep,
167
+ createHook,
168
+ createWebhook,
169
+ },
170
+ });
171
+ }
172
+ `;
173
+ }
174
+ export {
175
+ generateWorkflowDevkitArtifacts,
176
+ compileWorkflowSpecToWorkflowDevkit
177
+ };
@@ -0,0 +1,80 @@
1
+ // src/helpers.ts
2
+ import { evaluateExpression } from "@contractspec/lib.contracts-spec/workflow";
3
+ function inferWorkflowDevkitBehavior(step) {
4
+ return step.runtime?.workflowDevkit?.behavior ?? step.type;
5
+ }
6
+ function resolveWorkflowDevkitEntryStepId(spec) {
7
+ const entryStepId = spec.definition.entryStepId ?? spec.definition.steps[0]?.id;
8
+ if (!entryStepId) {
9
+ throw new Error(`Workflow ${spec.meta.key}.v${spec.meta.version} does not define an entry step.`);
10
+ }
11
+ return entryStepId;
12
+ }
13
+ function resolveWorkflowDevkitRunIdentity(spec, runIdentity) {
14
+ if (runIdentity) {
15
+ return runIdentity;
16
+ }
17
+ const strategy = spec.runtime?.workflowDevkit?.runIdentity?.strategy ?? "meta-key-version";
18
+ const prefix = spec.runtime?.workflowDevkit?.runIdentity?.prefix;
19
+ const baseIdentity = strategy === "meta-key-version" ? `${spec.meta.key}.v${spec.meta.version}` : `${spec.meta.key}.v${spec.meta.version}`;
20
+ return prefix ? `${prefix}:${baseIdentity}` : baseIdentity;
21
+ }
22
+ function resolveWorkflowDevkitWaitToken(spec, step, runIdentity) {
23
+ const runtime = step.runtime?.workflowDevkit;
24
+ if (!runtime) {
25
+ return;
26
+ }
27
+ const explicitToken = runtime.hookWait?.token ?? runtime.webhookWait?.token ?? runtime.approvalWait?.token ?? runtime.streamSession?.followUpToken;
28
+ if (explicitToken) {
29
+ return explicitToken;
30
+ }
31
+ const tokenStrategy = spec.runtime?.workflowDevkit?.hookTokens?.strategy ?? "deterministic";
32
+ const prefix = spec.runtime?.workflowDevkit?.hookTokens?.prefix ?? spec.meta.key;
33
+ const stableStepId = sanitizeIdentifier(step.id);
34
+ if (tokenStrategy === "session-scoped") {
35
+ const resolvedRunIdentity = sanitizeIdentifier(resolveWorkflowDevkitRunIdentity(spec, runIdentity));
36
+ return `${prefix}:${resolvedRunIdentity}:${stableStepId}`;
37
+ }
38
+ if (tokenStrategy === "step-scoped") {
39
+ return `${prefix}:v${spec.meta.version}:${stableStepId}`;
40
+ }
41
+ return `${prefix}:${stableStepId}`;
42
+ }
43
+ function resolveWorkflowDevkitNextStepId(spec, step, data, input, output) {
44
+ const transitions = spec.definition.transitions.filter((transition) => transition.from === step.id);
45
+ for (const transition of transitions) {
46
+ if (evaluateExpression(transition.condition, {
47
+ data,
48
+ input,
49
+ output
50
+ })) {
51
+ return transition.to;
52
+ }
53
+ }
54
+ return null;
55
+ }
56
+ function mergeWorkflowDevkitData(current, input, output) {
57
+ const next = { ...current };
58
+ if (isRecord(input)) {
59
+ Object.assign(next, input);
60
+ }
61
+ if (isRecord(output)) {
62
+ Object.assign(next, output);
63
+ }
64
+ return next;
65
+ }
66
+ function sanitizeIdentifier(value) {
67
+ return value.replace(/[^a-zA-Z0-9_-]+/g, "-");
68
+ }
69
+ function isRecord(value) {
70
+ return value != null && typeof value === "object" && !Array.isArray(value);
71
+ }
72
+ export {
73
+ sanitizeIdentifier,
74
+ resolveWorkflowDevkitWaitToken,
75
+ resolveWorkflowDevkitRunIdentity,
76
+ resolveWorkflowDevkitNextStepId,
77
+ resolveWorkflowDevkitEntryStepId,
78
+ mergeWorkflowDevkitData,
79
+ inferWorkflowDevkitBehavior
80
+ };